Skip to main content
Sonzai Docs
チュートリアル·約15分

カスタムステート&ワークフローイベント

カスタムステートを使用すると、エージェントの記憶と一緒に任意の構造化データを保存できます -- パフォーマンスメトリクス、タスク完了、マイルストーンフラグ、またはアプリケーション固有のコンテキストなど。ワークフローイベントにより、エージェントは会話の外で起きたこと(マイルストーン達成、タスク完了、目標達成)に反応できます。このチュートリアルを終える頃には、両方の読み書きと監視方法を理解できるようになります。

構築するもの

  • → 各セッション後に更新される、ユーザーの進捗スコアとティアを追跡するカスタムステート
  • → ユーザーがマイルストーンに到達したときに発火し、エージェントが反応するワークフローイベントトリガー
  • → ユーザーダッシュボード用のすべてのカスタムステートの一括読み取り
  • → バックエンドからの冪等なステート更新のためのupsertパターン

カスタムステートとは?

カスタムステートは、エージェント+ユーザー(またはエージェントのみ)にスコープされたキーバリューレコードです。値は任意のJSONシリアライズ可能な型です:文字列、数値、ブーリアン、配列、ネストされたオブジェクト。

記憶(会話から抽出される非構造化テキスト)とは異なり、カスタムステートはバックエンドから明示的に書き込む構造化データです。エージェントは会話中にget_custom_stateツール経由でこれを読み取れるため、ユーザーの現在のティア、ストリーク、残高などを常に把握できます。

カスタムステート(あなたが書き込み)

  • · 構造化JSONデータ
  • · バックエンドが制御
  • · タスク進捗、スコア、マイルストーン
  • · SDKまたはREST経由で更新

記憶(自動抽出)

  • · フリーフォームのテキストファクト
  • · プラットフォームがチャットから抽出
  • · 嗜好、イベント、目標
  • · 各メッセージ後に自動更新

1. カスタムステートの作成

ユーザーのステートを初めて設定するときはcreateを呼び出します。 以降の書き込みは冪等な更新のためにupsertを使用してください(ステップ3参照)。

import { Sonzai } from "@sonzai-labs/agents";

const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });
const AGENT_ID = "agent_abc";
const USER_ID  = "user_123";

const state = await client.agents.customStates.create(AGENT_ID, {
  userId: USER_ID,
  key:    "user_progress",
  value: {
    tier:            "silver",
    score:           2340,
    score_to_next:   3000,
    streak_days:     12,
    milestones:      ["first_chat", "50_tasks", "7_day_streak"],
  },
});

console.log("Created:", state.state_id, state.key);

2. チャット中にステートを読み取る

エージェントがget_custom_stateツールにアクセスできる場合(カスタムステートが存在すると自動的に有効化)、会話の開始時に現在のステートを取得します。バックエンドからもいつでも読み取れます。

// Read by key from your backend
const state = await client.agents.customStates.getByKey(AGENT_ID, {
  userId: USER_ID,
  key: "user_progress",
});

const progress = state.value as {
  tier: string; score: number; score_to_next: number; streak_days: number;
};

console.log(`${progress.tier} tier · ${progress.score}/${progress.score_to_next} pts · ${progress.streak_days}-day streak`);

会話中、エージェントはget_custom_state("user_progress") を呼び出し、進捗データを自然にレスポンスに組み込みます -- プロンプトの注入は不要です。

3. 冪等にステートをUpsert

ユーザーのステートが変化するたびに -- セッション終了後、購入後、またはスケジュールで -- バックエンドからupsertを使用します。upsertはステートが存在しなければ作成し、存在すれば置換します。

// Called after each work session ends
async function onSessionEnd(userId: string, sessionScore: number) {
  const current = await client.agents.customStates.getByKey(AGENT_ID, {
    userId,
    key: "user_progress",
  }).catch(() => null);

  const tiers = ["bronze", "silver", "gold", "platinum"];
  const prev = (current?.value ?? { tier: "bronze", score: 0, score_to_next: 1000, streak_days: 0 }) as {
    tier: string; score: number; score_to_next: number; streak_days: number; milestones: string[];
  };

  const newScore    = prev.score + sessionScore;
  const promoted    = newScore >= prev.score_to_next;
  const tierIndex   = tiers.indexOf(prev.tier);
  const newTier     = promoted ? (tiers[tierIndex + 1] ?? prev.tier) : prev.tier;

  await client.agents.customStates.upsert(AGENT_ID, {
    userId,
    key: "user_progress",
    value: {
      tier:          newTier,
      score:         promoted ? newScore - prev.score_to_next : newScore,
      score_to_next: promoted ? prev.score_to_next * 1.5 : prev.score_to_next,
      streak_days:   prev.streak_days + 1,
      milestones:    prev.milestones,
    },
  });

  if (promoted) {
    // Notify the agent so it can congratulate the user next session
    await client.agents.triggerGameEvent(AGENT_ID, {
      userId,
      eventType: "tier_promotion",
      payload: { new_tier: newTier, previous_tier: prev.tier },
    });
  }
}

4. ワークフローイベントのトリガー

ワークフローイベントにより、バックエンドから会話の外で起きたことをエージェントに伝えることができます。次にユーザーがチャットすると、エージェントは保留中のイベントを確認し、自然に反応します。

// Trigger from your backend when something notable happens
await client.agents.triggerGameEvent(AGENT_ID, {
  userId: USER_ID,
  eventType: "task_complete",
  payload: {
    task_name:     "Q1 Revenue Analysis",
    deliverable:   "Revenue Report",
    category:      "Analytics",
    time_taken:    "3h 42m",
  },
});

// Next time the user opens a conversation:
// Agent: "I see you finished the Q1 Revenue Analysis! That report is a key
//         deliverable. Want to discuss the findings or start the next task?"

イベント配信

ワークフローイベントはキューに入れられ、次の会話ターンで配信されます。アクティブなセッションを中断することはありません。エージェントは次のchatまたはchatStream呼び出しの開始時に保留中のイベントを消費し、オープニングメッセージまたは最初のレスポンスに組み込みます。

5. ユーザーの全ステートを一覧表示

管理ダッシュボード、ユーザープロフィールページ、またはデバッグの構築に便利です。エージェント+ユーザーペアのすべてのカスタムステートを返します。

const { states } = await client.agents.customStates.list(AGENT_ID, {
  userId: USER_ID,
});

for (const state of states) {
  console.log(`[${state.key}]`, JSON.stringify(state.value, null, 2));
}
// [user_progress]  { tier: "silver", score: 340, ... }
// [preferences]    { theme: "dark", notifications: true }
// [daily_summary]  { last_active: "2025-03-20", sessions_today: 2 }

6. 特定フィールドの更新

state_idでステートを変更したい場合はupdateを使用します。upsertと異なり、updateは部分マージを行います -- 変更したいフィールドのみを渡す必要があります。

// Add a milestone without overwriting the whole state
const state = await client.agents.customStates.getByKey(AGENT_ID, {
  userId: USER_ID,
  key: "user_progress",
});

const progress = state.value as { milestones: string[]; [k: string]: unknown };

await client.agents.customStates.update(AGENT_ID, state.state_id, {
  value: {
    ...progress,
    milestones: [...progress.milestones, "100_tasks"],
  },
});

7. ステートの削除

IDまたはキーでステートを削除します。次の会話では、エージェントはそのステートにアクセスできなくなります。

// Delete by key (finds and removes the state)
await client.agents.customStates.deleteByKey(AGENT_ID, {
  userId: USER_ID,
  key: "user_progress",
});

// Or delete by state_id if you already have it
await client.agents.customStates.delete(AGENT_ID, stateId);

よくあるパターン

オンボーディングステート

サインアップ時にonboardingステートを { step: 0, completed: false }で作成します。エージェントは初期の会話の開始時にこれを確認し、自然にユーザーをセットアップに案内します。

サブスクリプションコンテキスト

{ plan: 'pro', expires_at: '...' }を保存しておけば、毎回のチャットリクエストで渡さなくても、エージェントがどの機能を提供またはアップセルすべきかを把握できます。

日次サマリーキャッシュ

毎日の終わりに主要メトリクスを含むdaily_summaryステートを書き込みます。エージェントは翌日の会話をユーザーのアクティビティを参照して開始します -- 「昨日は3つのタスクを完了し、12日連続のストリークを達成しました。この調子で頑張りましょう!」

次のステップ

  • → 完全なAPIについては カスタムステート&ツール リファレンスを参照
  • → ステートに資産ポートフォリオを充実させるため インベントリ トラッキングを追加
  • → エージェントが特定のイベントをバックエンドにトリガーしたときに通知を受け取るようWebhookを設定
  • → イベントがエージェントの感情的な進化にどのように影響するかを確認するため パーソナリティ を確認