# Sonzai Documentation — Enterprise AI Agents Bundle > Auto-generated subset of the Sonzai docs optimised for LLM builders working on **Enterprise AI Agents**. > Pages where `` is used include only the enterprise tab. > For the full docs, see https://sonz.ai/llms-full.txt. Source: https://sonz.ai/docs/en # Advance Time Source: https://sonz.ai/docs/en/advance-time > Simulate time passing for an agent — fire schedules, generate diaries, decay mood, and replay what would have happened without waiting real time. Advance Time compresses real-world time into simulated time for an agent. Useful for character AI that needs in-game time to pass faster than real time, game loops that simulate days of agent state in seconds, or anywhere you want to see what the agent would be like after a period of elapsed time — without actually waiting for it. ## What you can build with it [#what-you-can-build-with-it] * **Character AI / visual novel time skips** — the protagonist sleeps for 8 hours; advance agent time by 8 hours and get the diary entry and mood changes that would have happened overnight * **Tamagotchi and life-sim game loops** — in-game days pass faster than real time; call `advanceTime` each tick to keep agent state (mood, memory, habits) in sync with the game clock * **Tutorial onboarding** — show a new user what their companion will "remember" after a week by fast-forwarding through a sample history before they send their first real message * **Deterministic replay** — reproduce the exact agent state after X hours at any time, for debugging, snapshotting, or building a save/load system * **Eval and benchmarking** — compress long-running scenarios into fast test runs (see [Also useful for evaluation](#also-useful-for-evaluation) below) ## Quickstart [#quickstart] Advance an agent's clock by 24 hours and inspect the diary entry generated for that simulated day. ## Core concepts [#core-concepts] ### What fires when time advances [#what-fires-when-time-advances] A single `advanceTime` call runs the full production background worker fleet for each complete 24-hour day in the window, then resolves any proactive wakeups due within it. Concretely: * **Diary generation** — one diary entry per simulated day, written from the agent's perspective * **Mood decay** — emotional state drifts toward the agent's baseline at the rate it would in real time * **Memory consolidation** — facts, events, and commitments are consolidated and deduplicated as they normally would be overnight * **Constellation extraction** — personality signals extracted from conversation history are processed on schedule * **Scheduled wakeups** — any wakeup whose `scheduled_at` falls inside the advance window fires with its intent Pass `simulatedHours: 25` (one day plus a sliver) when you need the weekly consolidation gate to tick over. ### Deterministic state transitions [#deterministic-state-transitions] Given the same agent state at the start, the same `advanceTime` call produces the same output. There is no randomness seeded from wall-clock time. This makes Advance Time suitable for save/load, replay, and regression testing. ### Async mode for long windows [#async-mode-for-long-windows] For advances that would exceed a proxy read timeout (Cloudflare's limit is \~100 s, which corresponds to roughly 4–5 simulated days depending on agent complexity), pass `runAsync: true`. The API returns immediately with a job descriptor; poll `getAdvanceTimeJob` until the status is terminal. ### Time granularity [#time-granularity] The smallest meaningful unit is one full 24-hour simulated day. Background jobs (diary, consolidation, constellation) run once per day. Sub-day advances (e.g. `simulatedHours: 8`) still process wakeups and mood decay but will not generate a diary entry unless a full day boundary is crossed. ## Full API [#full-api] | Method | Returns | Description | | -------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `advanceTime(options)` | `AdvanceTimeResponse` — or `{ job_id, status }` when async | Advance simulated time. Key fields: `days_processed`, `diary_entries`, `wakeups_fired`, consolidation counters. | | `getAdvanceTimeJob(jobId)` | `{ job_id, status, result?, error? }` | Poll an async advance-time job. `status` is `"running"`, `"succeeded"`, or `"failed"`. Job state has a 30-minute TTL. | **`advanceTime` options** | Field | Type | Required | Description | | ---------------------------------------------------------- | --------- | -------- | ------------------------------------------------------------------------- | | `agentId` / `agent_id` | `string` | Yes | Agent UUID | | `userId` / `user_id` | `string` | Yes | User ID — must match the ID used during chat | | `simulatedHours` / `simulated_hours` | `number` | Yes | Hours to advance | | `simulatedBaseOffsetHours` / `simulated_base_offset_hours` | `number` | No | Hours already processed by prior calls in the same gap (default `0`) | | `runAsync` / `run_async` / `async` | `boolean` | No | Return a job descriptor immediately instead of blocking (default `false`) | ## Combines with other features [#combines-with-other-features] ### With [Scheduled Reminders](./scheduled-reminders) — fast-forwarding pending reminders [#with-scheduled-reminders--fast-forwarding-pending-reminders] Any schedule whose `next_fire_at` falls within the advance window fires automatically. Advance 48 hours and two daily reminders will have fired — their intents processed, messages generated, and state updated — exactly as if real time had passed. ```ts // Create a daily 09:00 reminder await client.schedules.create("agent_abc", "user_123", { cadence: { simple: { frequency: "daily", times: ["09:00"] }, timezone: "UTC" }, intent: "check in on how the user is feeling", check_type: "reminder", }); // Advance 48 hours — both 09:00 fires trigger inside the window const result = await client.workbench.advanceTime({ agentId: "agent_abc", userId: "user_123", simulatedHours: 48, }); console.log(result.wakeups_fired); // 2 ``` ### With [Memory / Diary](./memory) — replay compressed history [#with-memory--diary--replay-compressed-history] When time advances, a diary entry is generated for each simulated day. The agent "remembers" what happened during the gap — emotional tone, recurring themes, relationship developments — the same way it would after real days of conversation. Use this to give a new user a companion that already feels lived-in, or to let a character "grow" between chapters of a story. ### With [Wakeups](./proactive-overview) — time-travel scheduled wakeups [#with-wakeups--time-travel-scheduled-wakeups] Any wakeup scheduled with a `scheduled_at` inside the advance window fires during the advance, including its LLM-generated proactive message. This lets you test wakeup copy and timing without waiting for the real clock to reach the fire time. ## Tutorials [#tutorials] Advance Time is a primitive that chains with scheduled reminders, wakeups, and memory. There is no standalone end-to-end tutorial yet. See the linked Relationship Layer pages below for how it combines with other features. ## Next steps [#next-steps] * [Scheduled Reminders](./scheduled-reminders) — schedules fire automatically during a time advance * [Memory](./memory) — diary entries are generated for each simulated day in the window * [Evaluation](./evaluation) — if you are using Advance Time for benchmarking, see the eval workflow *** ## Also useful for evaluation [#also-useful-for-evaluation] If you are running a benchmark suite, `advanceTime` lets you compress long-running scenarios into fast test runs. Advance a simulated week in seconds, inspect the diary entries and mood state, then score the result. Pair with the [evaluation workflow](./evaluation) to measure agent behavior quality after arbitrary amounts of simulated elapsed time. --- # Agent Insights Source: https://sonz.ai/docs/en/agent-insights > Fetch what the agent has learned about each user — habits, goals, interests, relationships, diary entries, and constellation clusters. Derived signals from the context engine. As the agent talks to a user over time, it builds up a derived view of who they are — what they care about, what they're working toward, who's in their life, and how their mood trends. Agent Insights exposes that derived state as readable (and for some signals, writable) endpoints. These are not things you author; the context engine extracts them automatically from conversations. All insight signals are produced by the context engine during and after each conversation. You do not need to call any write endpoint to populate them — they fill in on their own. The read endpoints on this page let you surface what the agent has learned. ## What you can build with it [#what-you-can-build-with-it] * **Personalized dashboards** — show the user exactly what the agent has learned about them, building transparency and trust * **Weekly wrap-ups** — "here's what's on your mind this week" compiled from diary entries and top interests * **Relationship-aware UX** — surface the list of people the agent knows about so users can review or correct them * **Goal-tracking integrations** — sync agent-tracked goals to external task managers or CRMs after they are detected * **Engagement health scoring** — aggregate breakthroughs, mood trend, and habit streaks into a single user health metric ## Quickstart [#quickstart] Fetch habits, goals, and interests for a user in one pass. ## Core concepts [#core-concepts] **Derived, not authored.** These signals are extracted from conversation text by the context engine. You do not push them in; the agent surfaces them automatically as it talks. **Per-instance scoping.** Pass `instanceId` (TS/Python) or `instanceID` (Go) to filter results to a specific agent instance — useful when an agent is deployed in multiple scenarios or chat contexts for the same user. **Write endpoints for some signals.** Goals and habits can be explicitly created, updated, or deleted when your application needs to drive a specific state (e.g., seeding a goal when a user starts onboarding, or marking a goal achieved after a purchase event). Interests, relationships, diary, constellation, and breakthroughs are read-only. **Read latency.** Derived signals update at conversation turn-end, not in real time during a turn. Reads immediately after a chat call may not yet reflect the latest turn. ## Full API [#full-api] ### Read endpoints [#read-endpoints] | Method | Go | Returns | Description | | ------------------------------------------------------ | ------------------- | ----------------------- | ------------------------------------------------ | | `listHabits(agentId, { userId?, instanceId? })` | `ListHabits` | `HabitsResponse` | Extracted recurring behaviors | | `listGoals(agentId, { userId?, instanceId? })` | `ListGoals` | `GoalsResponse` | Active and achieved goals | | `getInterests(agentId, { userId?, instanceId? })` | `GetInterests` | `InterestsResponse` | Topics and themes the user cares about | | `getRelationships(agentId, { userId?, instanceId? })` | `GetRelationships` | `RelationshipsResponse` | People mentioned across conversations | | `getDiary(agentId, { userId?, instanceId? })` | `GetDiary` | `DiaryResponse` | Agent-authored diary entries per session | | `getConstellation(agentId, { userId?, instanceId? })` | `GetConstellation` | `ConstellationResponse` | Memory clusters (nodes, edges, insights) | | `listBreakthroughs(agentId, { userId?, instanceId? })` | `ListBreakthroughs` | `BreakthroughsResponse` | Significant emotional or relationship milestones | ### Write endpoints (goals and habits) [#write-endpoints-goals-and-habits] Goals and habits support full CRUD. All other insight types are read-only. | Method | Go | Description | | ---------------------------------------- | ------------- | --------------------------------------- | | `createGoal(agentId, opts)` | `CreateGoal` | Seed a goal before or during a workflow | | `updateGoal(agentId, goalId, opts)` | `UpdateGoal` | Change status, priority, or description | | `deleteGoal(agentId, goalId, opts?)` | `DeleteGoal` | Soft-delete (abandon) a goal | | `createHabit(agentId, opts)` | `CreateHabit` | Manually seed a habit | | `updateHabit(agentId, habitName, opts)` | `UpdateHabit` | Update strength or description | | `deleteHabit(agentId, habitName, opts?)` | `DeleteHabit` | Remove a habit | ## Habits [#habits] Habits are recurring behaviors the context engine detects across conversations — things like "user meditates in the morning" or "user reviews their tasks every Sunday." Each habit has a `strength` (0-1) that rises with observations and a `formed` flag that is set once the habit is considered stable. ## Goals [#goals] Goals represent what the user is working toward. They are extracted automatically from conversation intent — "I want to run a 5K by June" becomes a goal with a `type`, `title`, and `priority`. Goals have a `status` field: `active`, `achieved`, or `abandoned`. ## Interests [#interests] Interests are topics and themes the context engine identifies as meaningful to the user — things like "machine learning", "hiking", or "Italian cooking." Unlike goals, interests have no lifecycle status; they accumulate over time. ## Relationships [#relationships] Relationships are the people the user mentions across conversations — friends, family, colleagues, and others the agent has learned about. Each entry includes the person's name, their relationship to the user, and any context the agent has collected. ## Diary [#diary] The diary contains agent-authored entries written at session end — reflections on what happened, what was learned, and how the relationship is evolving. Each entry is anchored to a session and a timestamp. Diary entries are the richest narrative signal available. ## Constellation [#constellation] The constellation is the agent's knowledge graph for a user — a set of nodes (concepts, people, themes) and edges (relationships between them) that the context engine builds from recurring patterns across memory. Nodes have a `significance` score and a `node_type`. ## Breakthroughs [#breakthroughs] Breakthroughs are significant relationship or emotional milestones detected by the platform — moments where the agent's understanding of the user meaningfully deepened, or where a notable shift in the relationship dynamic was recorded. ## Combines with other features [#combines-with-other-features] ### With [Memory](./memory) — insights are summaries over raw facts [#with-memory--insights-are-summaries-over-raw-facts] Insight signals are derived summaries; the underlying evidence lives in memory. Fetch habits to learn what patterns exist, then use `memory.search` to pull the raw conversation facts behind one of them. ### With [Emotions](./emotions) — mood + insights for a full user picture [#with-emotions--mood--insights-for-a-full-user-picture] `getMood` and these insight endpoints together form the agent's complete understanding of a user at a point in time. Fetch both to power a user-facing "how the agent sees you" view or a support dashboard. ### With [Advance Time](./advance-time) — replay insight formation [#with-advance-time--replay-insight-formation] Advance Time fast-forwards the context engine's processing — generating new diary entries, decaying mood, and updating derived signals — without waiting real time. This is useful for simulating what the agent would know after a period of elapsed time, and for testing insight endpoints against a populated state. ```typescript // Advance 7 days to populate diary entries and update insights const result = await client.workbench.advanceTime({ agentId: "agent_abc", userId: "user_123", simulatedHours: 168, }); // Now read the insights that formed during that window const diary = await client.agents.getDiary("agent_abc", { userId: "user_123" }); console.log("Diary entries after 7d:", diary.entries.length); ``` ## Tutorials [#tutorials] * [Memory](./memory) — the raw facts behind insights; use `memory.search` to drill into any signal * [Emotions](./emotions) — mood, mood history, and aggregate mood statistics * [Personality](./personality) — Big5 traits and personality evolution (a different kind of derived state) * [Advance Time](./advance-time) — fast-forward the agent's processing to simulate elapsed time --- # Architecture Source: https://sonz.ai/docs/en/architecture > How the Relationship Layer Platform fits into your application architecture. **The Relationship Layer architecture** separates agent intelligence from your application logic: your backend keeps owning auth, business state, and user data, while Sonzai owns [personality](./personality), [memory](./memory), mood, habits, and relationships behind a REST API. A single chat call assembles context, streams the AI response, and updates every internal state automatically — no extra orchestration calls. The most load-bearing thing to know: post-chat learning runs on Sonzai's side, so your backend never schedules consolidation, mood decay, or fact extraction. ## System Overview [#system-overview] The Relationship Layer is a standalone platform that separates agent intelligence (personality, memory, mood) from your application logic. Any backend integrates via REST API or the official SDKs. {` Your Backend Relationship Layer Platform | | |--- Create Agent ---------------->| |<-- Agent ID + Profile -----------| | | |--- Chat (SSE streaming) -------->| | (messages + app context) |-- Build context |<-- Streaming AI response --------|-- Stream AI response | |-- Update memory, mood, personality |<-- Proactive notifications -------| (automatic, no extra calls) `} ## Integration Architecture [#integration-architecture] A typical deployment has three layers: ## What the Platform Manages [#what-the-platform-manages] On each chat call, the platform automatically assembles relevant context from personality, memory, mood, and relationship data before generating the AI response. Post-chat state updates happen automatically — no extra API calls needed. Personality, mood, memories, relationship narrative, and application state — all assembled per request. Facts, events, and commitments are extracted from each conversation and stored automatically. Mood and Big5 personality drift naturally based on interaction patterns. Agents can schedule proactive outreach between sessions. Deliver via polling or webhook. ## Data Ownership [#data-ownership] The Relationship Layer and your backend each own distinct data: **Relationship Layer Owns** * Agent personality profiles * Memory facts and summaries * Mood state (happiness, energy, calmness, affection) * Personality evolution history * Habits and goals * Relationship narratives * Knowledge Base entities and graphs * Custom agent state **Your Backend Owns** * User authentication * Business logic and workflows * User profiles and preferences * Application data and state * Billing and subscriptions * Permissions and access control * Session management ## Session Lifecycle [#session-lifecycle] {` 1. User opens chat Your Backend prepares application context (user data, preferences...) 2. Chat happens Your Backend ---> Chat SDK call (context + messages) User <--- Streaming AI response tokens 3. Chat ends Platform updates: memory, mood, personality, habits, relationships 4. Between sessions Platform runs: background consolidation, mood decay, proactive wakeups `} ## Background Processing & Self-Improvement [#background-processing--self-improvement] The platform doesn't just respond to chat calls — it runs a continuous background pipeline that keeps memory accurate, behavioral state coherent, and retrieval quality climbing over time. Every loop runs automatically; nothing for you to schedule or wire. | Cadence | What runs | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Every turn** | Importance + confidence updates, mood adjustments, personality micro-shifts, habit observations, association strengthening, source-anchoring checks | | **Every session end** | Fact extraction with verification, duplicate consolidation, next-session prediction, retrieval policy updates, pattern learning, session quality scoring, topic-shift audit | | **Daily** | Memory decay (importance, confidence, relationships, habits), memory tree self-organization and pruning, deep consolidation, cluster reconciliation, goal consolidation, reflective diary, convergence checks | | **Weekly** | Narrative arc compression, association decay, cross-reference detection, warm-start for new agent–user pairs, learning-pace check | | **Continuous** | Adaptive retrieval budget, memory recovery, return prediction, background interest research, recurring event detection, smart memory selection | For a complete walk-through of every mechanism — including consolidation, reversible deduplication, boundary detection, personality drift safety caps, breakthroughs, and the cautious-rollout system — see [How Agents Improve Over Time](/docs/en/self-improvement). ## SDK Integration Points [#sdk-integration-points] Use the official SDKs to interact with every part of the platform: * **Agents**: `create, get, list, update` * **Chat**: `chat, chatStream (SSE)` * **Memory**: `seed, search, list, browse, timeline, listFacts, reset` * **Personality**: `get, update, history` * **Mood**: `get, history, aggregate` * **Knowledge Base**: `createSchema, insertFacts, bulkUpdate, search, recommendations, trends` * **Custom States**: `create, get, upsert, list, delete` * **Custom Tools**: `create, list, delete (agent-level and session-level)` * **Notifications**: `list, consume, history` * **User Priming**: `primeUser, batchImport, getMetadata, updateMetadata` --- # Pattern 6: Hermes Source: https://sonz.ai/docs/en/connect-hermes > Drop the sonzai-hermes plugin pair into a Hermes Agent project. Sonzai becomes the agent's MemoryProvider and ContextEngine — persistent memory, mood, personality, and relationship state, under Hermes' existing tool and orchestration loop. [Hermes Agent](https://hermes-agent.nousresearch.com) is the open-source agent framework from Nous Research. It exposes two pluggable extension points — `MemoryProvider` and `ContextEngine` — that together control what an agent remembers and what survives a context-window compression. The `sonzai-hermes` package ships both implementations as a paired drop-in: install once, and every Hermes turn flows through the Relationship Layer. ## When to use this [#when-to-use-this] * You're already running Hermes Agent (or your team has standardised on it). * You want Hermes' existing chat loop, tool plugins, and orchestration to keep working — Sonzai only owns memory + context compression. * You want Hermes' own loader to discover the plugins through its standard ABCs (no monkey-patching, no forks). ## When to switch [#when-to-switch] * **Not on Hermes** — switch to [Pattern 1: Managed Runtime](/docs/en/connect-managed-runtime) (we run the chat), [Pattern 3: OpenClaw](/docs/en/connect-openclaw) (different framework), or [Pattern 4: Standalone Realtime](/docs/en/connect-standalone-realtime) (you run the chat with one of our SDKs). * **No real-time chat at all** — switch to [Pattern 5: Standalone Batch](/docs/en/connect-standalone-batch). ## One-shot install [#one-shot-install] ```bash pip install sonzai-hermes sonzai-hermes install # stages both plugins into Hermes' discovery paths sonzai-hermes setup # provisions a 14-day trial if no key, writes $HERMES_HOME/.env ``` `sonzai-hermes install` stages the **Memory Provider** into `$HERMES_HOME/plugins/sonzai/` (Hermes' supported user path) and the **Context Engine** into the Hermes install tree's `plugins/context_engine/sonzai/` (Hermes' loader only scans the bundled tree for engines today). It's idempotent and safe to re-run after `pip install --upgrade hermes-agent`. Then in your Hermes profile (`~/.hermes/config.yaml`): ```yaml memory: provider: sonzai context: engine: sonzai ``` Restart Hermes and the agent has persistent memory and Sonzai-driven context compression. Already have a Sonzai key? Set `SONZAI_API_KEY` before `setup` and the trial flow is skipped. ## Architecture [#architecture] {` Hermes Agent Runtime sonzai-hermes plugins Sonzai Relationship Layer | | | |-- start_session(user, agent) ->| MemoryProvider | | |-- resolve session -------->| | |<-- ranked memory ----------| | | | |-- on_turn(user_message) ----->| | | |-- recall + persist ------->| | |<-- structured memory ------| | | | | [LLM call w/ memory context] | | | | | |-- compact(ctx_window) -------->| ContextEngine | | |-- consolidation ---------->| | |<-- compressed window ------| | | (not a naive summary) | | | | |-- end_session(session_id) ---->| | | |-- extract facts, update | | | mood + personality ----->| `} ## BYOK — bring your own LLM provider keys [#byok--bring-your-own-llm-provider-keys] If `OPENAI_API_KEY`, `GEMINI_API_KEY` (or `GOOGLE_API_KEY`), `XAI_API_KEY`, or `OPENROUTER_API_KEY` are set in your environment, both plugins automatically register them with the Sonzai platform on first startup. Sonzai then routes LLM calls through **your** provider account, charging only the 25% service fee instead of the \~125% platform-key markup. The registration is idempotent; subsequent startups are no-ops if nothing changed. Override per provider with `SONZAI_BYOK__KEY` (takes precedence over the standard env var name). Set `SONZAI_PROJECT_ID` if your tenant has multiple projects and none is named `Default`. ## CLI reference [#cli-reference] | Command | What it does | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `sonzai-hermes install` | Stages both plugins into Hermes' discovery paths. | | `sonzai-hermes install --memory-only` / `--engine-only` | Install one without the other. | | `sonzai-hermes install --symlink` | Symlink instead of copy — best for dev / editable installs. | | `sonzai-hermes setup` | If no `SONZAI_API_KEY`: provisions a 14-day trial and writes the key to `$HERMES_HOME/.env`. | | `sonzai-hermes status` | Show what's currently staged. | | `sonzai-hermes claim` | Convert a trial into a permanent account (prints + opens claim URL). | | `sonzai-hermes uninstall` | Reverse both installs. | Flags `--hermes-home` and `--hermes-src` override the auto-detected locations when Hermes lives somewhere non-standard. Hermes treats memory and context-window compression as separate extension points (`MemoryProvider` ABC + `ContextEngine` ABC). We ship both so the relationship loop stays whole: turn-level recall plus consolidation-based compression, both talking to the same Sonzai session. Use `--memory-only` or `--engine-only` if you want just one. ## Where to next [#where-to-next] --- # Pattern 1: Managed Agent Runtime Source: https://sonz.ai/docs/en/connect-managed-runtime > The highest-level path. You hand Sonzai a user message, Sonzai returns a streamed reply, and memory, mood, personality, and tool execution all happen on our side. You point your app at `client.agents.chat` (or open an explicit session with `sessions.start` → chat turns → `sessions.end`). Sonzai assembles the system prompt from the agent's identity, recalls relevant memories, runs the LLM, streams tokens back, executes any registered tools, and updates state — all in a single call. **You write the least code in this pattern.** It is the right default for chat companions, support agents, and anything where Sonzai owning the full agent loop is acceptable. ## When to use this [#when-to-use-this] * You want one HTTP call per turn and zero memory plumbing. * You're happy letting Sonzai pick (or accept your override of) the LLM provider. * You want personality, mood, memory, voice, KB search, and proactive notifications to all "just work" without orchestrating them yourself. ## When to switch [#when-to-switch] * **Your own LLM is non-negotiable** — switch to [Pattern 4: Standalone Realtime](/docs/en/connect-standalone-realtime). * **Conversation already happens off-platform** (recorded calls, transcripts, batch ingest) — switch to [Pattern 5: Standalone Batch](/docs/en/connect-standalone-batch). * **Inside Claude Desktop / Cursor / an MCP-compatible IDE** — switch to [Pattern 2: MCP](/docs/en/connect-mcp). * **Already using OpenClaw** — switch to [Pattern 3: OpenClaw](/docs/en/connect-openclaw). ## Architecture [#architecture] {` ┌─────────────┐ ┌───────────────────────────────────┐ │ Your App │ │ Sonzai │ └──────┬──────┘ └──────────────────┬────────────────┘ │ │ │ agents.chat({ messages }) │ │───────────────────────────────>│ • assemble context │ │ (memory, mood, │ │ personality, KB, │ │ relationship) │ │ • run LLM (your choice │ │ of provider/model) │ │ • execute registered │ │ tools (if any) │ <── SSE stream ───────────────│ • write back: facts, │ tokens + done │ mood, personality, │ │ goals, habits │ │ │ (optional) sessions.end │ │───────────────────────────────>│ • consolidate, dedup, │ │ diary, clustering `} ## End-to-end snippet [#end-to-end-snippet] The simplest complete flow: open an explicit session, drive a streaming chat, end the session. If you don't call `sessions.start`, Sonzai opens one on the first `agents.chat` call and closes it on idle. The session ID still flows through to extracted facts. Use the explicit lifecycle when you need session-scoped tools, predictable boundaries, or replay semantics. ## Where to next [#where-to-next] --- # Pattern 2: MCP Source: https://sonz.ai/docs/en/connect-mcp > Connect Claude Code, Cursor, Claude Desktop, ChatGPT, or any MCP-compatible client directly to the Relationship Layer. Your assistant sees Sonzai as a set of tools and resources it can call by name. The Relationship Layer ships a hosted **Streamable HTTP** MCP endpoint at `https://api.sonz.ai/mcp/memory/{agent_id}`. Point any MCP-compatible client at it with your Sonzai API key — **34 tools**, 4 resources, and 3 guided prompts. No local binary, no SSE port, no Go toolchain. ## When to use this [#when-to-use-this] * The user is already inside Claude Code, Cursor, Claude Desktop, ChatGPT, or another MCP-aware client. * You want to drive Sonzai by conversation rather than by SDK code. * You're prototyping — pick the `create-companion` or `mind-layer-setup` guided prompt and skip writing any code at all. ## When to switch [#when-to-switch] * **Building your own product UI** — switch to [Pattern 1: Managed Runtime](/docs/en/connect-managed-runtime). * **You want Sonzai inside your own LLM loop, not as a tool exposed to someone else's** — switch to [Pattern 4: Standalone Realtime](/docs/en/connect-standalone-realtime). ## Architecture [#architecture] {` ┌────────────────────────┐ ┌──────────────────────┐ │ Claude Code · Cursor │ │ Sonzai Relationship Layer │ │ ChatGPT · VS Code │ │ │ │ Claude Desktop │ │ │ └──────────┬─────────────┘ └──────────┬───────────┘ │ │ │ Streamable HTTP (JSON-RPC 2.0) │ │ • list_agents │ │ • chat / start_session / end_session │ │ • search_memories / list_facts │ │ • get_personality / get_mood │ │ • generate_character / trigger_event │ │ • schedule_wakeup / list_notifications │ │ │ ▼ │ https://api.sonz.ai/mcp/memory/{agent_id} │ Authorization: Bearer sk-your-api-key │ │ ▼ Context Engine, AI Service, DBs `} ## End-to-end snippet [#end-to-end-snippet] You need a project API key from your [dashboard](https://platform.sonz.ai/dashboard/projects) and an agent ID. Pick your client below — pasting the snippet is the entire setup. The 2026 MCP spec marks Streamable HTTP as the canonical remote transport. SSE is on a deprecation path across major clients — prefer HTTP for any new integration. The Bearer token is your project API key — it grants full access to every agent in the project. Don't commit it to public repos; use per-developer scopes when collaborating. ## Where to next [#where-to-next] --- # Pattern 3: OpenClaw Source: https://sonz.ai/docs/en/connect-openclaw > Drop the @sonzai-labs/openclaw-context plugin into an OpenClaw project and Sonzai becomes the agent's contextEngine — persistent memory, mood, personality, relationships, all under OpenClaw's existing chat loop. [OpenClaw](https://openclaw.ai) is an open-source framework for building conversational AI agents through a slot-based plugin system. The slot that decides what context goes into the system prompt is called `contextEngine`. Installing `@sonzai-labs/openclaw-context` registers the **Sonzai context engine** under the name `"sonzai"` — assign it to the slot in `openclaw.json` and every conversation flows through the Relationship Layer with zero additional code. ## One-shot install [#one-shot-install] ```bash npx --yes @sonzai-labs/openclaw-context install ``` This single command probes backend health, runs `openclaw plugins install`, launches the interactive setup wizard, and writes your `openclaw.json` — end to end. With no existing Sonzai key, it provisions a 14-day trial automatically (no sign-up, no browser); claim it into a permanent account any time with `npx @sonzai-labs/openclaw-context claim`. Restart OpenClaw and the agent now has persistent memory, mood, personality, and relationship state out of the box. ## When to use this [#when-to-use-this] * You're already building on OpenClaw, or your team has standardised on it. * You want OpenClaw's existing chat loop, telemetry, and tool plugins to keep working — Sonzai only swaps the memory/personality layer. * You want a `` block injected into the system prompt on every turn, automatically priority-ordered and budget-trimmed. ## When to switch [#when-to-switch] * **Not on OpenClaw** — switch to [Pattern 1: Managed Runtime](/docs/en/connect-managed-runtime) (we run the chat) or [Pattern 4: Standalone Realtime](/docs/en/connect-standalone-realtime) (you run the chat). * **No real-time chat at all** — switch to [Pattern 5: Standalone Batch](/docs/en/connect-standalone-batch). ## Architecture [#architecture] {` OpenClaw Runtime SonzaiContextEngine Sonzai Relationship Layer | | | |-- bootstrap(sessionId) ------->| | | |-- resolve agent + session->| | |<-- session state ----------| | | | |-- assemble(messages, budget) ->| | | |-- fetch memory, mood, | | | personality, goals ----->| | |<-- ranked context blocks --| |<-- systemPromptAddition -------| priority-ordered, | | | token-budget-trimmed | | | | | [LLM call w/ enriched prompt] | | | | | |-- afterTurn(sessionId) ------->| | | |-- send transcript -------->| | | Relationship Layer extracts | | | facts, updates mood, | | | evolves personality | | | | |-- compact(sessionId) --------->| | | |-- merge short → long term->| `} ## End-to-end snippet [#end-to-end-snippet] The OpenClaw plugin is JavaScript-only (OpenClaw itself is JS). The Python and Go branches show the equivalent **B2B provisioning** flow: deterministically derive an agent UUID and write the OpenClaw config — the runtime that consumes it stays JS. Agent IDs are derived from `SHA1(tenantID + agentName)`. Calling `setup()` (or the Python/Go equivalent) multiple times for the same tenant + name returns the same agent — safe to re-run on every deploy. ## Where to next [#where-to-next] --- # Pattern 5: Standalone Memory (Batch) Source: https://sonz.ai/docs/en/connect-standalone-batch > One call after the conversation is done. Ship the full transcript to /process (or sessions.end with messages) and let Sonzai extract facts, mood, personality, and habits in the background. You own the entire conversation. Sonzai never sees it in real time. When the conversation ends — call wraps, support case closes, journaling session finishes — you POST the transcript once and Sonzai's extractor turns it into facts, mood updates, personality drift, habit detection, and proactive-outreach signal. Best for tutoring, fitness, CRM, voice calls, journaling, and any flow where Sonzai in the hot path is undesirable or impossible. ## When to use this [#when-to-use-this] * Latency budget can't tolerate a per-turn `/turn` round-trip. * The transcript already exists (recorded calls, Gong/Zoom exports, journal entries). * You want bulk ingest after the fact — replay logs, migrate users, benchmark agent quality. ## When to switch [#when-to-switch] * **You want fresh per-turn context** — [Pattern 4: Standalone Realtime](/docs/en/connect-standalone-realtime). * **You're happy ceding the LLM call too** — [Pattern 1: Managed Runtime](/docs/en/connect-managed-runtime). ## Architecture [#architecture] {` ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ │ Your App │ │ Sonzai API │ │ Your LLM │ └──────┬──────┘ └────────┬─────────┘ └──────┬───────┘ │ │ │ │ GET /context │ │ │────────────────────>│ (optional pre-session │ │ <── user profile ──│ personalization) │ │ │ │ │ ══ Your conversation (Sonzai not involved) ═════════│ │ │ │ │ │ Chat ──────────────┼──────────────────────>│ │ │ <── reply ─────────┼───────────────────────│ │ │ [N turns, your loop, your tools] │ │ │ │ │ │ │ ════════════════════════════════════════════════════════│ │ │ │ │ /process or sessions.end({ messages }) │ │────────────────────>│── extract facts, │ │ (full transcript) │ personality, mood, │ │ │ habits, interests │ │ <── extractions ───│ (Sonzai LLM) │ │ │ │ │ Use insights │ │ │ (push notif, │ │ │ dashboard, │ │ │ exercises, …) │ │ └─────────────────────┴───────────────────────┘ `} ## End-to-end snippet [#end-to-end-snippet] The simplest path is `/process`: one call, auto-creates the session, returns the generated `session_id` for correlation. Use the explicit `sessions.start → end({ messages })` lifecycle when you need session-scoped tools, durations, or async polling. `/process` and `sessions.end({ messages })` are functionally equivalent for batch ingest — both extract facts and side effects from the full transcript inline. **Don't do both** for the same transcript or extraction runs twice. Use `/process` for the simple one-call shape. Use `sessions.start` + `sessions.end({ messages })` when you want explicit lifecycle, async polling, or session-scoped tools. `/process` and `sessions.end` are intentionally lightweight: extract facts and a session summary inline (one LLM call per chunk). The expensive cross-session work (dedup, clustering, diary, decay) is scheduled automatically by the platform — you don't pay for it on every call. ## Where to next [#where-to-next] --- # Pattern 4: Standalone Memory (Real-Time) Source: https://sonz.ai/docs/en/connect-standalone-realtime > You own the LLM and the chat loop. Sonzai owns memory, mood, personality, and relationships. Per-turn — sessions.start → loop of session.context() + your LLM + session.turn() → sessions.end. You keep your existing chat loop. Before each LLM call, you ask Sonzai for the enriched context for the user's message; after the LLM replies, you submit just that exchange via `session.turn()`. Mood lands inline (\~300–500 ms). Deeper extraction — facts, personality drift, habit detection, goal updates — runs asynchronously 5–15 seconds later in the background. **Sonzai never sees your tool execution and never picks your model.** This is the right shape for chat companions, voice agents, agent frameworks (OpenAI Agents SDK, LangChain, LiveKit), and anywhere you already had a working LLM loop in production before adopting Sonzai. ## When to use this [#when-to-use-this] * You already have a production LLM loop with custom tools, evals, prompt templates, or a specific provider. * You need fresh per-turn context, not a once-a-conversation pull. * You want mood, facts, personality, habits, goals, and relationship signal — without ceding LLM choice or tool execution. ## When to switch [#when-to-switch] * **One conversation can't end fast enough to wait for `.turn()` per exchange** — switch to [Pattern 5: Standalone Batch](/docs/en/connect-standalone-batch). * **Sonzai owning the LLM call is fine** — switch to [Pattern 1: Managed Runtime](/docs/en/connect-managed-runtime) and delete most of this code. ## Architecture [#architecture] {` ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ │ Your App │ │ Sonzai API │ │ Your LLM │ └──────┬──────┘ └────────┬─────────┘ └──────┬───────┘ │ │ │ │ sessions.start │ │ │────────────────────>│ (prewarms memory) │ │ <── Session ───────│ │ │ │ │ │ ─── Per turn ──────────────────────────── │ │ │ │ │ session.context() │ │ │────────────────────>│ │ │ <── enriched ctx ──│ │ │ personality, mood│ │ │ memories, goals │ │ │ │ │ │ Your LLM loop ─────┼──────────────────────>│ │ + your tools │ │ │ + your multimodal │ │ │ <── reply ─────────┼───────────────────────│ │ │ │ │ sendToUser(reply) (no waiting on Sonzai) │ │ │ │ │ session.turn() │ │ │────────────────────>│ ⇒ sync mood ~300ms │ │ <── mood, status ──│ ⇒ background extract │ │ │ (5–15s) │ │ │ │ │ ─── Repeat ────────────────────────────── │ │ │ │ │ session.end() │ │ │────────────────────>│── consolidate │ │ │ long-term memory │ └─────────────────────┴───────────────────────┘ `} ## End-to-end snippet [#end-to-end-snippet] The minimum viable loop with a real harness. The OpenAI Agents SDK owns conversation state, model selection, and tool dispatch. Sonzai sits *outside* that loop: it supplies the system prompt via `session.context()` before the run, and ingests the finished exchange via `session.turn()` after. No `OPENAI_API_KEY` needed — the Agents SDK is pointed at Gemini's OpenAI-compat endpoint. Always call `session.context(query=user_msg)` **before** the LLM call — every turn. That's the closing-the-loop step. Skipping it means the LLM works from stale state and the value of a memory layer collapses. `session.turn()` accepts `fetchNextContext: { query: nextMessage }` (Python: `fetch_next_context={"query": ...}`). When set, the response carries the next `/context` payload under `next_context`, so the client already has turn N+1's context by the time turn N finishes. ## Tool calls flow through to extraction [#tool-calls-flow-through-to-extraction] Sonzai's `/turn` accepts OpenAI/Anthropic-style tool messages: `tool_calls` on assistant messages and `role: "tool"` results. Forward the **full** exchange and the extractor can capture facts that only surfaced inside a tool output (e.g. *"user's last order shipped from Tokyo"* from an `order-lookup` tool). ```python session.turn(messages=[ {"role": "user", "content": "Where did my last order ship from?"}, { "role": "assistant", "content": None, "tool_calls": [{ "id": "call_1", "type": "function", "function": {"name": "order-lookup", "arguments": "{}"}, }], }, {"role": "tool", "tool_call_id": "call_1", "content": '{"order_id":"42","origin":"Tokyo","carrier":"DHL"}'}, {"role": "assistant", "content": "Your last order shipped from Tokyo via DHL."}, ]) ``` Sonzai never executes a tool — that's your harness's job. It just reads the messages you submit. If you're on the OpenAI Agents SDK, see the demo's [`run_result_to_sonzai_messages`](https://github.com/sonz-ai/sonzai-python/blob/main/demos/openai_agents_companion/app.py) helper — it converts a `Runner` result's `MessageOutputItem` / `ToolCallItem` / `ToolCallOutputItem` items into this shape. ## Multimodal: your harness sees pixels, Sonzai sees text [#multimodal-your-harness-sees-pixels-sonzai-sees-text] `/turn` accepts text content only. **This is intentional, not a limitation.** Memory is a layer of *semantic understanding* — the question Sonzai needs to answer later is "what does this agent know about this user?", not "what bytes did the LLM see?". Your vision-capable LLM has already understood the image; pass that understanding to Sonzai as text, and the memory pipeline can extract facts, habits, and inventory items from it like any other turn. The recommended pattern: have your same multimodal LLM produce a short factual description alongside its warm reply, and embed that description in the user message you submit to `session.turn()`. ```python # Your harness: Gemini sees the actual image bytes via input_image. result = await gemini.chat.completions.create( model="gemini-3.1-flash-lite-preview", messages=[ {"role": "system", "content": SYSTEM_PROMPT_IMAGE_AWARE}, # see below {"role": "user", "content": [ {"type": "text", "text": user_msg}, {"type": "image_url", "image_url": {"url": image_url}}, ]}, ], ) raw = result.choices[0].message.content # Dual-output: split the reply (shown to user) from the [MEMORY: ...] note. memory_note, reply = split_memory_note(raw) # your tiny parser send_to_user(reply) # Sonzai sees: the original user text + a description of the image. # It will extract facts like "user goes to the gym", "wore a black tank top". session.turn(messages=[ {"role": "user", "content": f"{user_msg}\n\n[Image attached: {memory_note}, URL: {image_url}]"}, {"role": "assistant", "content": reply}, ]) ``` The `SYSTEM_PROMPT_IMAGE_AWARE` instruction is what makes this work — it asks the LLM to emit a hidden line like `[MEMORY: ]` after its warm reply. Same LLM call, no extra cost or latency, no second roundtrip. The same pattern works for **audio** (send the transcript) and **assistant-generated images** (describe what you generated). For the full pattern with all three SDKs, see the [deep guide's multimodal section](/docs/en/guides/standalone-memory/pattern-1-realtime#working-with-images--multimodal-input). If a tool returns a screenshot, file blob, or any non-text payload, apply the same rule: have your harness summarize what the tool returned in a one-line text result before forwarding the `role: "tool"` message to `session.turn()`. ## Skipping local history with `recent_turns` [#skipping-local-history-with-recent_turns] If your harness already keeps a message log (most do — Agents SDK, LangChain, etc.), use that. If you'd rather not maintain one, every `/context` response carries `recent_turns` — the raw messages buffered by `/turn` for the current session, in chronological order. Read them straight off `ctx.recent_turns` and feed them to your LLM: ```python ctx = session.context(query=user_message) history = [{"role": t["role"], "content": t["content"]} for t in (ctx.get("recent_turns") or [])] reply = your_llm.chat( system=build_system_prompt(ctx), messages=[*history, {"role": "user", "content": user_message}], ) ``` The buffer is **per-session and text-only** — no tool calls, no images, no system prompts. It's the right shape for a simple chat loop where Sonzai is the source of truth; if you need richer message structure, keep your own. ## Where to next [#where-to-next] --- # Conversations Source: https://sonz.ai/docs/en/conversations > Send messages to an agent and stream back responses over SSE — the primary loop that drives memory, mood, and personality evolution with every turn. Every feature in the Sonzai platform flows through the conversation loop. Each turn sends messages to the agent, streams back a response, and automatically updates memory, mood, relationships, personality, and goals — no separate API calls required. ## What you can build with it [#what-you-can-build-with-it] * **Chat UIs** (web, mobile) — stream tokens directly into your interface * **Voice conversations** — combine with [Voice](./voice) for spoken responses * **Tool-using agents** — combine with [Custom Tools](./custom-tools) to let the agent call your functions * **Streaming responses** — Server-Sent Events keep UI latency low * **Multi-instance conversations** — scope memory and state per scenario with `instanceId` * **Background task agents** — non-streaming chat for job queues and automation ## Quickstart [#quickstart] ### Simple chat [#simple-chat] ### Streaming chat [#streaming-chat] Stream tokens as they arrive for a more responsive experience. The platform sends OpenAI-compatible SSE chunks; each line starts with `data:` and the stream closes with `data: [DONE]`. ## Core concepts [#core-concepts] ### Chat vs ChatStream [#chat-vs-chatstream] `Chat` aggregates the full response before returning — simpler to wire into job queues and one-shot automation. `ChatStream` (and `ChatStreamChannel` in Go) streams tokens as they arrive, which keeps UI latency low and lets you render responses progressively. Both call the same underlying SSE endpoint; `Chat` just buffers the events internally. ### Session scoping [#session-scoping] Pass `sessionId` to group messages into a traceable conversation. If you omit it, the platform assigns one automatically. Use your own session ID convention (e.g. `case-`) so you can join conversation logs to your internal systems. ### Tool capabilities per-chat [#tool-capabilities-per-chat] Built-in tools (`web_search`, `image_generation`, `remember_name`, `inventory`) are opt-in. Pass `toolCapabilities` on each chat call to enable them for that request. Custom tools defined on the agent are always available; platform-managed tools (memory, state) run automatically without configuration. ### Instance ID for multi-scenario [#instance-id-for-multi-scenario] `instanceId` isolates memory and personality evolution to a specific context — e.g. per-workspace or per-game-session. Without it, state is scoped per user. Set it consistently across calls that belong to the same scenario. ### Message role conventions [#message-role-conventions] Use `"user"` for the end-user's turn and `"assistant"` for prior agent replies you want to include as history. The platform appends the new assistant turn automatically after each successful chat call — you don't need to re-inject it. ### max\_turns for multi-message replies [#max_turns-for-multi-message-replies] Set `maxTurns` to control how many assistant messages the agent produces in a single call. Default is `1`. Raise it for companions that send a few short messages in a row; keep it at `1` for task agents that should give a single focused reply. ### Platform-managed state updates [#platform-managed-state-updates] After every turn, the platform automatically runs: * **Memory extraction** — facts, events, and commitments extracted from the conversation * **Mood update** — detected emotional themes shift mood dimensions * **Personality evolution** — gradual Big5 shifts from interaction patterns * **Habit tracking** — recurring patterns become tracked habits * **Relationship update** — chemistry and relationship narrative updated * **Goal progress** — recorded progress on active goals No separate API calls are required. ## Full API [#full-api] All chat methods live on `client.agents.*` (TS/Python) or `client.Agents` (Go). | Method | Returns | Description | | ----------------------------------- | --------------------------------------------------- | ------------------------------------------- | | `chat(opts)` | `ChatResponse` | Aggregated non-streaming chat | | `chatStream(opts)` | `AsyncIterable` (TS) / `iter` (Py) | SSE streaming — tokens as they arrive | | `Chat(ctx, params)` | `*ChatResponse, error` | Go aggregated chat (buffers SSE internally) | | `ChatStream(ctx, params, callback)` | `error` | Go SSE streaming with per-event callback | | `ChatStreamChannel(ctx, params)` | `<-chan ChatStreamEvent, <-chan error` | Go SSE streaming via Go channel | ### ChatOptions fields [#chatoptions-fields] | Field | Type | Description | | ---------------------- | ----------------------- | ----------------------------------------------------------- | | `messages` | `ChatMessage[]` | Conversation history including the new user turn | | `userId` | `string` | Identifies the user; scopes per-user memory and state | | `sessionId` | `string` | Groups messages into a traceable conversation | | `instanceId` | `string` | Isolates state to a scenario (workspace, game session) | | `language` | `string` | BCP-47 language tag for the response | | `timezone` | `string` | IANA timezone used for time-aware responses | | `maxTurns` | `int` | Maximum assistant messages per call (default: 1) | | `toolCapabilities` | `AgentToolCapabilities` | Opt-in built-in tools (image generation, web search, …) | | `compiledSystemPrompt` | `string` | Per-request application context injected into system prompt | | `provider` / `model` | `string` | Override the LLM provider or model for this call | ## Combines with other features [#combines-with-other-features] ### With [Wakeups](./wakeups) — proactive messages appear in the same chat stream [#with-wakeups--proactive-messages-appear-in-the-same-chat-stream] Wakeups let the agent initiate contact outside the normal request-response cycle. When you poll for or receive a wakeup delivery, feed it into your chat UI as an agent-initiated message so the user sees it inline. ```typescript // 1. Schedule a one-off wakeup await client.agents.wakeups.create("agent-id", { userId: "user-123", checkType: "interest_check", intent: "follow up on the project the user mentioned yesterday", delayHours: 24, }); // 2. Poll for pending proactive messages and surface them in the chat UI const pending = await client.agents.notifications.list("agent-id", { userId: "user-123", }); for (const msg of pending.messages) { renderAgentBubble(msg.content); // proactive message lands inline } ``` ### With [Scheduled Reminders](./scheduled-reminders) — recurring proactive messages surface in chat [#with-scheduled-reminders--recurring-proactive-messages-surface-in-chat] Scheduled reminders fire on a cadence and deliver proactive agent messages. Poll `notifications.list` after each reminder fires to pull the generated message into your chat UI, creating a continuous conversation thread that spans both reactive and proactive turns. ```typescript // 1. Create a daily check-in schedule await client.schedules.create("agent-id", "user-123", { cadence: { simple: { frequency: "daily", times: ["09:00"] }, timezone: "Asia/Singapore" }, intent: "morning check-in on mood and energy", checkType: "reminder", }); // 2. At fire time, the platform generates a proactive message — fetch it const pending = await client.agents.notifications.list("agent-id", { userId: "user-123", }); pending.messages.forEach(msg => renderAgentBubble(msg.content)); ``` ### With [Custom Tools](./custom-tools) — agent calls your functions during chat [#with-custom-tools--agent-calls-your-functions-during-chat] Register custom tools on the agent and pass `toolCapabilities` on the chat call. When the agent decides to invoke a tool, the SSE stream emits a `tool_call` event — your client executes the function and can feed the result back in a follow-up turn. ```typescript // 1. Register a custom tool on the agent (one-time setup) await client.agents.createCustomTool("agent-id", { name: "get_order_status", description: "Returns the current status of a customer order", parameters: { type: "object", properties: { order_id: { type: "string" } }, required: ["order_id"], }, }); // 2. Enable it in the chat call — agent can now invoke it for await (const event of client.agents.chatStream({ agent: "agent-id", messages: [{ role: "user", content: "Where's my order #4521?" }], userId: "user-123", })) { if (event.type === "tool_call") { const result = await getOrderStatus(event.toolCall.parameters.order_id); console.log("Tool result:", result); // feed back in next turn } else { process.stdout.write(event.choices?.[0]?.delta?.content ?? ""); } } ``` ### With [Memory](./memory) — every turn writes memory, search retrieves it [#with-memory--every-turn-writes-memory-search-retrieves-it] The platform extracts facts from every conversation turn automatically. You can query memory immediately after a chat call to confirm capture, or use `memory.search` to build a context widget showing what the agent remembers about the user. ```typescript // Chat turn — platform captures facts automatically const response = await client.agents.chat({ agent: "agent-id", messages: [{ role: "user", content: "I just signed up for a marathon in June." }], userId: "user-123", }); // Memory search — retrieves facts just captured (and prior ones) const memories = await client.agents.memory.search("agent-id", { query: "marathon running plans", userId: "user-123", limit: 5, }); for (const result of memories.results) { console.log(result.content, result.score); // "User signed up for a marathon in June" 0.92 } ``` ## Tutorials [#tutorials] * [Getting started](./guides/getting-started) — first chat call end-to-end * [Quickstart](./guides/getting-started) — five-minute TypeScript, Python, or Go setup ## Next steps [#next-steps] * [Sessions](./sessions) — explicit session management and history * [Voice](./voice) — combine chat with spoken audio responses * [Memory](./memory) — how facts flow from conversation turns into long-term storage * [Custom Tools](./custom-tools) — give the agent callable functions * [Scheduled Reminders](./scheduled-reminders) — proactive agent-initiated messages on a cadence --- # Custom State Source: https://sonz.ai/docs/en/custom-states > Per-user counters, flags, and strings the agent reads and writes — energy, currency, progress flags, game state — with a stable schema you control. Custom State is simple structured per-user data the agent can read and modify during conversations. Use it for counters, flags, or any state your product tracks per user. Unlike memory (which the platform extracts from conversation text), Custom State is data you write explicitly from your backend — and the agent sees it immediately. ## What you can build with it [#what-you-can-build-with-it] * **Game loops** — energy, currency, turn counters, progression flags * **Feature flags** — per-user toggles for experimental features * **Session-scoped state** — timers, streaks, active-quest identifiers * **Progress markers** — "completed onboarding", "has premium", "saw-tutorial-X" * **Rate limits / quotas** — message counts, daily-action remaining ## Quickstart [#quickstart] Create an `energy` state for a user, starting at 100. ## Core concepts [#core-concepts] ### Typed values [#typed-values] Every state has a `content_type` that tells the platform how to interpret `value`: | `content_type` | Value | Example | | ------------------ | -------------------------- | ------------------------------------ | | `"text"` (default) | string | `"active"`, `"silver"` | | `"json"` | any JSON-serializable type | `{ "score": 340, "tier": "silver" }` | | `"binary"` | base64-encoded bytes | raw binary payloads | ### Scoping model [#scoping-model] All states are scoped to an `instanceId` — one deployment context of your agent (e.g. a workspace or game world). Omit `instanceId` to use the default instance. See [Instances](/docs/en/instances) for details. ### Agent reads and writes [#agent-reads-and-writes] When the agent has access to custom states, it reads current state at the start of each conversation via the `get_custom_state` tool — no prompt injection required. The agent can also update state during a conversation if you define a [Custom Tool](./custom-tools) that calls your backend. ### Distinct from Inventory [#distinct-from-inventory] | | Custom State | Inventory | | -------- | ---------------------------------- | --------------------------------------------------------------- | | Shape | Simple typed field | Structured item with a KB-linked schema | | Use case | Counters, flags, strings | Items with multiple properties (medications, holdings, pets) | | Schema | You define the key + content\_type | Defined in your Knowledge Base | | Best for | `energy: 80`, `tier: "gold"` | `{ name: "Metformin", dose_mg: 500, frequency: "twice daily" }` | Use Custom State for primitives and simple objects. Reach for [Inventory](./inventory) when items have their own identity, multiple typed fields, and a shared schema across users. ## Full API [#full-api] ### Create [#create] ### Upsert (create or update by key) [#upsert-create-or-update-by-key] `Upsert` creates the state if the key doesn't exist, or replaces the value if it does. Idempotent — safe to call on every update cycle from your backend. ### Get by key [#get-by-key] Retrieve a specific state by its composite key (key + scope + user\_id + instance\_id). ### List [#list] Return all states for an agent, optionally filtered by scope or user. ### Update by state ID [#update-by-state-id] Update a state you already have the `state_id` for. Only `value` and `content_type` can be changed. ### Delete [#delete] Delete by state ID or by composite key. ### Method summary [#method-summary] | Method | Returns | Description | | ------------------------------------- | -------------------------- | ------------------------------------- | | `Create(ctx, agentID, opts)` | `*CustomState` | Create a new state entry | | `Upsert(ctx, agentID, opts)` | `*CustomState` | Create or replace by composite key | | `GetByKey(ctx, agentID, opts)` | `*CustomState` | Fetch one state by key + scope | | `List(ctx, agentID, opts)` | `*CustomStateListResponse` | List states, filtered by scope / user | | `Update(ctx, agentID, stateID, opts)` | `*CustomState` | Update value by state ID | | `Delete(ctx, agentID, stateID)` | — | Delete by state ID | | `DeleteByKey(ctx, agentID, opts)` | — | Delete by composite key | ## Combines with other features [#combines-with-other-features] ### With [Custom Tools](./custom-tools) — tools that read and write state [#with-custom-tools--tools-that-read-and-write-state] Define a tool that lets the agent trigger a state change from inside a conversation. Your backend executes the tool call and calls `upsert` to apply the new value. ```typescript await client.agents.sessions.setTools("agent-id", "session-id", [ { name: "spend_energy", description: "Deduct energy from the user. Call when the user takes an action that costs energy.", parameters: { type: "object", properties: { amount: { type: "number", description: "Energy to deduct (1–50)" }, }, required: ["amount"], }, }, ]); // In your tool handler: // 1. Receive externalToolCall { name: "spend_energy", arguments: { amount: 10 } } // 2. Read current energy with getByKey // 3. Upsert the new value // 4. Return the result in the next chat message ``` ### With [Inventory](./inventory) — when state is structured, use inventory [#with-inventory--when-state-is-structured-use-inventory] Custom State is the right tool for primitive values and simple flat objects: `energy: 80`, `tier: "gold"`, `onboarding_complete: true`. When a piece of data has its own identity, multiple typed properties, and a shared schema across users — a medication, a stock holding, a pet — use Inventory instead. | Situation | Use | | --------------------------------------------------- | ------------ | | Single number or string per key | Custom State | | A flag that is true/false | Custom State | | A flat object with a few fields | Custom State | | An item with a schema defined in the Knowledge Base | Inventory | | A collection of items of the same type per user | Inventory | ### With [Sessions](./sessions) — session-scoped vs persistent state [#with-sessions--session-scoped-vs-persistent-state] Custom State is persistent by default — it survives across sessions and is visible in every future conversation. If you need state that only exists for the duration of one conversation (a temporary form-fill context, a one-time confirmation token), scope it at the session level instead by passing it in the chat request's context fields rather than writing it as a Custom State. ## Tutorials [#tutorials] * [Custom States walkthrough](./tutorial-custom-states) — end-to-end example: create, upsert, read during chat, trigger events on state changes ## Next steps [#next-steps] --- # Custom Tools Source: https://sonz.ai/docs/en/custom-tools > Define functions the LLM can call during chat — built-in platform capabilities, persistent agent-level tools, and ephemeral session tools injected at runtime. Custom Tools let the LLM invoke functions during inference. Sonzai handles `sonzai_`-prefixed built-in tools automatically. Custom tools are defined by you and executed by your backend — Sonzai surfaces the call as a side effect in the SSE stream. If you use [standalone memory mode](/docs/en/guides/standalone-memory) (BYO-LLM), Sonzai exposes tool schemas you can wire into your agent framework (LangChain, Vercel AI SDK, Gemini function calling, etc.). See the [Tool Integration guide](/docs/en/guides/tool-integration) for details. ## What you can build with it [#what-you-can-build-with-it] * **Expressive companion actions** — emote, change outfit, move scene, give a gift * **Backend integrations** — `create_ticket`, `lookup_order`, `schedule_meeting` * **State mutations** — tools that read or write [Custom State](./custom-states) on behalf of the agent * **Approval-gated workflows** — propose an action, your backend validates before executing * **Context-sensitive tools** — inject different tool sets per session depending on user role or screen ## Quickstart [#quickstart] Register a session-level tool and handle its call in the next chat turn. ## Core concepts [#core-concepts] ### Built-In Tools (Capabilities) [#built-in-tools-capabilities] Toggle platform-managed capabilities per agent. These are enabled at agent creation or updated via the capabilities API. Searches stored memories during inference. Auto-injected into context. Persists the user's name for future conversations. On by default. Live web search via Google. On by default. Read user resource items and join with Knowledge Base data. The `sonzai_` prefix is reserved. Your custom tools must not use it — the API will reject them. ### `customTools` in agent capabilities [#customtools-in-agent-capabilities] `AgentCapabilities` includes a `customTools` field — a snapshot of the agent-level custom tools currently registered. Use `get_capabilities()` to read them, or use the dedicated `list_custom_tools()` / `createCustomTool()` methods (shown in the Full API section below) to manage them. ### Tool scoping [#tool-scoping] | Type | Scope | Persistence | Managed Via | | -------------------- | ------------- | ---------------- | --------------------------- | | Built-in (`sonzai_`) | All instances | Platform-managed | SDK capabilities, Dashboard | | Agent-level custom | All instances | Persistent | SDK, Dashboard | | Session-level | Per session | Temporary | SDK (inline or setTools) | ## Full API [#full-api] ### Custom Tools (Agent-Level) [#custom-tools-agent-level] Persistent tools stored with the agent and available in every chat, regardless of session or instance. ### Session-Level Tools (temporary) [#session-level-tools-temporary] Inject tools dynamically for a specific session. Session tools merge with agent-level tools — same-name session tools take precedence. Discarded when the session ends. #### Option 1 — Set for an existing session [#option-1--set-for-an-existing-session] #### Option 2 — Pass inline with the chat call [#option-2--pass-inline-with-the-chat-call] ### Handling Tool Calls [#handling-tool-calls] When the LLM decides to call a custom tool, it appears as a side effect in the SSE stream. Your backend executes the tool and returns the result in the next message. #### 1. Receive the tool call [#1-receive-the-tool-call] #### 2. Execute and return results [#2-execute-and-return-results] ## In Practice [#in-practice] What you expose as tools differs sharply by use case — keep descriptions vivid and tightly scoped so the LLM invokes them naturally. **Tools are business-logic actions with approval gates.** Never let the agent write to your system of record unilaterally for anything material — wrap destructive or high-stakes tools with an approval step. ```typescript await client.agents.sessions.setTools("agent-id", "session-id", [ { name: "propose_stage_change", description: "Propose moving the deal to a new stage. Requires rep approval before the CRM is updated.", parameters: { /* JSON Schema */ }, }, { name: "flag_for_legal_review", description: "Flag this conversation for legal review. Halts the agent and notifies compliance.", parameters: { /* JSON Schema */ }, }, ]); ``` **Pattern: propose, don't execute.** Tools return a proposed action, your backend validates + applies it, and the audit trail captures the agent's intent separately from the human's approval. ## Combines with [#combines-with] ### [Custom State](./custom-states) — what tools often act on [#custom-state--what-tools-often-act-on] Define a tool that lets the agent trigger a state change from inside a conversation. Your backend executes the tool call and calls `upsert` to apply the new value. ```typescript await client.agents.sessions.setTools("agent-id", "session-id", [ { name: "spend_energy", description: "Deduct energy from the user. Call when the user takes an action that costs energy.", parameters: { type: "object", properties: { amount: { type: "number", description: "Energy to deduct (1–50)" }, }, required: ["amount"], }, }, ]); // In your tool handler: // 1. Receive externalToolCall { name: "spend_energy", arguments: { amount: 10 } } // 2. Read current energy with getByKey // 3. Upsert the new value // 4. Return the result in the next chat message ``` ### [Sessions](./sessions) — session-scoped vs persistent tools [#sessions--session-scoped-vs-persistent-tools] Agent-level tools persist across all sessions. Session-level tools are injected at runtime and discarded when the session ends — use them when the available tool set depends on the current screen, user role, or conversation context. ### [Conversations](./conversations) — tool calls in the message stream [#conversations--tool-calls-in-the-message-stream] Tool calls appear as side effects in the SSE stream. See the [Conversations](./conversations) page for the full event shape and streaming patterns. ## Tutorials [#tutorials] * [Custom States walkthrough](./tutorial-custom-states) — end-to-end example that includes a `spend_energy` tool writing back to Custom State ## Next steps [#next-steps] --- # Emotions & Mood Source: https://sonz.ai/docs/en/emotions > Read, analyze, and time-travel through an agent's living mood state — a four-dimensional signal that shifts automatically with every conversation, event, and passage of time. Mood is a four-dimensional value (happiness, energy, calmness, affection) that the context engine maintains automatically for every agent-user pair. Every conversation, application event, and time-based decay is processed without any code on your side. The APIs on this page are for **reading** that state (dashboards, UI, analytics) or **time-traveling** to understand what it looked like at a past moment. Mood, emotions, and goals are all managed automatically by the context engine. You do not push deltas or set mood manually — you read what the engine has already computed. ## What you can build with it [#what-you-can-build-with-it] * **Mood-aware UI** — show the agent's current mood label and dimension values so users can read emotional state at a glance * **Mood history graphs** — plot happiness, energy, calmness, or affection over time to surface relationship phases * **Mood-influenced response tuning** — the engine already bakes mood into every reply; you can also use the live signal to adjust your own UI (avatar expression, ambient sound, tint) * **Aggregate mood over time for cohort analysis** — roll up mood across all users for a given agent to track product-level sentiment health * **Time-machine replay** — fetch mood as it stood at any past timestamp for audit trails or narrative moments ("we were in a tough place three weeks ago") ## Quickstart [#quickstart] Fetch the current mood for an agent-user pair. ## Core concepts [#core-concepts] ### Four mood dimensions [#four-mood-dimensions] Each agent-user pair carries a mood state with four independent dimensions, each on a 0–100 scale: | Dimension | Low end | High end | | ------------- | ------------------- | --------------------- | | **Happiness** | Sad / distressed | Joyful / blissful | | **Energy** | Lethargic / flat | Active / enthusiastic | | **Calmness** | Anxious / unsettled | Peaceful / at ease | | **Affection** | Distant / reserved | Warm / affectionate | The overall mood **label** is derived from the combined dimensions: `Blissful` (80–100), `Content` (60–79), `Neutral` (40–59), `Melancholy` (20–39), `Troubled` (0–19). ### What shifts mood automatically [#what-shifts-mood-automatically] * **Chat interactions** — the engine detects emotional themes in each turn (e.g. `joy_blooming`, `feeling_overwhelmed`) and adjusts dimensions accordingly. * **Application events** — achievements, outings, and returns trigger mood shifts without your code doing anything. * **Time-based decay** — mood drifts back toward the agent's personality-derived baseline over time. ### History vs current vs aggregate [#history-vs-current-vs-aggregate] * **Current** (`GetMood`) — the live snapshot for a single user right now. * **History** (`GetMoodHistory`) — a time-series of snapshots for one user, suitable for graphs and narrative moments. * **Aggregate** (`GetMoodAggregate`) — rolled-up statistics across all users for an agent, suitable for cohort dashboards. ### Time Machine [#time-machine] `GetTimeMachine` returns the agent's full state — mood, personality, and evolution events — as it stood at any past UTC timestamp. The response carries `mood_at` (the mood state at that moment), `personality_at` (personality state then), `current_personality` (today's state for comparison), `evolution_events` (what changed in between), and `requested_at` (the timestamp you queried). ## Full API [#full-api] All mood methods are on `client.agents.*` (TS/Python) or `client.Agents` (Go). Full request/response shapes live in the [API reference](./api-reference). | Method | Returns | Description | | -------------------------------------------------------------- | ------------------------ | ------------------------------------ | | `GetMood(ctx, agentID, userID, instanceID)` | `*MoodResponse` | Current mood for a user | | `GetMoodHistory(ctx, agentID, userID, instanceID)` | `*MoodHistoryResponse` | Time-series of mood snapshots | | `GetMoodAggregate(ctx, agentID, userID, instanceID)` | `*MoodAggregateResponse` | Aggregated mood stats across users | | `GetTimeMachine(ctx, agentID, TimeMachineOptions{At, UserID})` | `*TimeMachineResponse` | Full agent state at a past timestamp | ## Combines with other features [#combines-with-other-features] ### With [Self-Improvement](./self-improvement) — mood feeds personality evolution [#with-self-improvement--mood-feeds-personality-evolution] The self-improvement engine reads sustained mood patterns and extracts evolution events that gradually reshape the agent's personality. You can observe this pipeline in action by fetching mood history alongside recent evolution events. ### With [Agent Insights](./agent-insights) — mood is part of the "what the agent learned" picture [#with-agent-insights--mood-is-part-of-the-what-the-agent-learned-picture] Agent Insights surfaces what the agent has understood about a user across all sessions. Mood is a key dimension of that picture — pairing a current mood read with insights lets you build a complete emotional-state panel. ### With [Advance Time](./advance-time) — fast-forward mood decay [#with-advance-time--fast-forward-mood-decay] In the workbench or integration tests, you can advance the clock to observe how mood decays back toward baseline without waiting for real time to pass. Read mood before and after to see the delta. ## Tutorials [#tutorials] * [Memory tutorial](./tutorial-memory) — see how conversation content flows into mood and memory together. ## Next steps [#next-steps] * [Self-Improvement](./self-improvement) — how sustained mood patterns drive personality evolution * [Agent Insights](./agent-insights) — the full picture of what the agent knows about a user * [Personality](./personality) — the baseline that mood decays toward * [Advance Time](./advance-time) — fast-forward the clock in tests and the workbench --- # Events & Multi-Agent Dialogue Source: https://sonz.ai/docs/en/events-and-dialogue > Trigger agent reactions to backend events and run conversations between two agents — for achievements, milestones, NPC interactions, or automated simulations. Your backend knows things the agent doesn't: a user just levelled up, an order shipped, a milestone was hit. **TriggerEvent** lets you push those signals to an agent and get a tailored reaction — no user message required. **Dialogue** lets you orchestrate two agents talking to each other, turn by turn, so you can build NPC conversations, run evaluation simulations, or script automated specialist hand-offs. Both primitives use the same enriched context pipeline as regular chat — the agent draws on memory, personality, and mood when it responds. ## What you can build with it [#what-you-can-build-with-it] ### Events [#events] * **Level-up celebrations** — your game backend detects a rank change and fires a `level_up` event; the agent congratulates the user in its own voice * **Daily summaries** — a cron job fires a `daily_summary` event with session stats in `metadata`; the agent writes a personalised recap * **Achievement unlocks** — trigger a proactive message the moment a user hits a milestone, so the agent's enthusiasm lands while the moment is fresh * **External state changes** — order shipped, appointment confirmed, subscription renewed; the agent reacts to your system events rather than waiting for the user to ask ### Multi-Agent Dialogue [#multi-agent-dialogue] * **NPC interactions** — two character AIs converse while the user watches; each agent stays in its own voice and draws on its own memory * **Simulation runs** — iterate N agents through scripted scenarios for offline evaluation without a real user in the loop * **Specialist hand-offs** — agent A poses a question to agent B and incorporates the answer before responding to the user *** ## Quickstart — Trigger an event [#quickstart--trigger-an-event] Fire a `level_up` event with structured metadata. The agent generates a reaction and the platform queues it for delivery through the same channels as other proactive messages. *** ## Quickstart — Run a dialogue [#quickstart--run-a-dialogue] Dialogue is a per-agent call. To run a conversation between two agents, you orchestrate turns yourself: call agent A, append its response to the message history, call agent B with that updated history, and so on. Each agent independently draws on its own memory and personality. *** ## Core concepts [#core-concepts] ### Events [#events-1] **EventType is free-form.** There is no fixed enum. Common conventions used by tenants: `"achievement"`, `"daily_summary"`, `"level_up"`, `"order_shipped"`, `"appointment_confirmed"`, `"milestone"`. Pick names that are meaningful in your domain and stay consistent across your backend. **EventDescription is for the LLM.** Write it as plain-English narration: `"The user just cleared chapter 5 for the first time after 3 failed attempts."` The agent's underlying model reads this and uses it to shape the reaction — be specific rather than terse. **Metadata is string-only.** The `metadata` map accepts `string → string` pairs only. For nested or numeric data, either serialize into the `event_description` or flatten it with explicit keys (`"xp_gained"`, `"xp_total"`, `"level_before"`, `"level_after"`). **Messages field grounds the event in a prior conversation.** If the event is closely tied to a conversation that just ended (for example, a `daily_summary` fired after a chat session), pass the recent messages. The platform uses them directly for context-sensitive generation — diary entries, summaries — instead of relying on lossy consolidation. Omit this field for cron-driven events that have no associated conversation. **TriggerEventResponse** contains two fields: * `accepted` (`bool`) — whether the platform accepted the event for processing * `event_id` (`string`) — an opaque identifier for the queued event; store it if you want to correlate platform logs ### Dialogue [#dialogue] **Each call is per-agent.** The `dialogue` method is scoped to a single agent: you pass an `agentId` and the current message history. To model a conversation between two agents, you manage the turn loop — append each response to the shared `messages` slice and alternate which `agentId` you call. **Messages carry the full context.** Unlike `chat`, which manages conversation history server-side per session, `dialogue` expects you to pass the full message thread with every call. You control the window. **sceneGuidance steers both tone and constraints.** Pass a brief instruction describing the scene and any constraints (`"keep responses under 3 sentences"`, `"the agents are rivals"`, `"agent_a does not know about the treasure"`) so both sides stay in character. **requestType signals the call's purpose.** An optional free-form tag (`"npc_scene"`, `"eval_round"`, `"specialist_consult"`) that downstream analytics can use for filtering. Has no effect on generation. **DialogueResponse** contains: * `response` (`string`) — the agent's generated text for this turn * `side_effects` — optional structured metadata emitted by the agent (tool calls, mood signals, etc.) *** ## Full API [#full-api] | Method | Returns | Description | | ------------------------------------------------------------------------------------------------------------------ | ---------------------- | ------------------------------------------------ | | `triggerBackendEvent(agentId, opts)` · `trigger_backend_event(agent_id, ...)` · `TriggerEvent(ctx, agentID, opts)` | `TriggerEventResponse` | Fire a backend event and queue an agent reaction | | `dialogue(agentId, opts)` · `dialogue(agent_id, ...)` · `Dialogue(ctx, agentID, opts)` | `DialogueResponse` | Generate one turn of agent dialogue | **TriggerEventOptions / trigger\_backend\_event kwargs:** | Field | Type | Required | Description | | ------------------------------------------------------------- | --------------------------------------------------------------- | -------- | --------------------------------------------- | | `userId` / `user_id` / `UserID` | `string` | Yes | The user this event belongs to | | `eventType` / `event_type` / `EventType` | `string` | Yes | Free-form event name, e.g. `"level_up"` | | `eventDescription` / `event_description` / `EventDescription` | `string` | No | Plain-English narration for the LLM | | `metadata` / `Metadata` | `Record` / `dict[str,str]` / `map[string]string` | No | Structured string-only metadata | | `language` / `Language` | `string` | No | Locale override (e.g. `"ja"`) | | `instanceId` / `instance_id` / `InstanceID` | `string` | No | Instance scope | | `messages` / `Messages` | `ChatMessage[]` | No | Recent conversation that triggered this event | **DialogueOptions / dialogue kwargs:** | Field | Type | Required | Description | | ---------------------------------------------------- | --------------- | -------- | ---------------------------------------- | | `userId` / `user_id` / `UserID` | `string` | No | User context for the agent | | `messages` / `Messages` | `ChatMessage[]` | No | Full conversation history for this turn | | `sceneGuidance` / `scene_guidance` / `SceneGuidance` | `string` | No | Instruction scoping tone and constraints | | `requestType` / `request_type` / `RequestType` | `string` | No | Tag for analytics (e.g. `"eval_round"`) | | `instanceId` / `instance_id` / `InstanceID` | `string` | No | Instance scope | *** ## Combines with other features [#combines-with-other-features] ### With [Proactive Messaging](./proactive-overview) — events as the dev-controlled push source [#with-proactive-messaging--events-as-the-dev-controlled-push-source] [Proactive Messaging](./proactive-overview) has three sources: Scheduled Reminders (recurring cadence), Wakeups (one-off timed), and TriggerEvent (your backend fires it when something happens). TriggerEvent is the push-based source you control directly — no schedule required, no timer running. When the event is accepted, the platform routes the generated reaction through the same delivery channels as the other two sources: SSE if the user has an active stream, the polling notifications API, or your registered webhook. ```ts // Proactive triangle in code form: // Source 1 — recurring schedule (time-based) await client.schedules.create("agent_abc", "user_123", { cadence: { simple: { frequency: "daily", times: ["09:00"] }, timezone: "Asia/Tokyo" }, intent: "morning check-in", check_type: "reminder", }); // Source 2 — one-off wakeup (time-based) await client.agents.scheduleWakeup("agent_abc", { user_id: "user_123", check_type: "appointment_reminder", intent: "remind the user about their dentist appointment", delay_hours: 2, }); // Source 3 — TriggerEvent (you push it when something happens) await client.agents.triggerBackendEvent("agent_abc", { userId: "user_123", eventType: "appointment_confirmed", eventDescription: "The user just confirmed their 3pm dentist appointment for tomorrow.", }); ``` ### With [Conversations](./conversations) — Messages field grounds the event in context [#with-conversations--messages-field-grounds-the-event-in-context] When a TriggerEvent fires immediately after a chat session — for example, a `daily_summary` event at session end — pass the recent conversation messages in the `messages` field. The platform uses them directly as conversation history for context-sensitive generation (diary entries, personality updates) instead of relying on condensed consolidation summaries. The agent's reaction then references what was actually said rather than a lossy reconstruction. ```ts // After a chat session ends, fire a daily_summary event with the full message history const sessionMessages = [ { role: "user", content: "I finally finished that project I was stressing about." }, { role: "assistant", content: "That's huge! You've been working on that for weeks." }, { role: "user", content: "Yeah. Feels good. Think I'll take the evening off." }, ]; await client.agents.triggerBackendEvent("agent_abc", { userId: "user_123", eventType: "daily_summary", eventDescription: "Session ended. User shared a work win and plans to rest.", messages: sessionMessages, // grounds the summary in what was actually said }); ``` ### With [Evaluation](./evaluation) — Dialogue as a scoring harness [#with-evaluation--dialogue-as-a-scoring-harness] Run a judge agent and a subject agent in a dialogue loop to score the subject's responses without a real user. The judge poses questions, the subject answers, and you feed both transcripts to your evaluation rubric. This lets you evaluate agent quality at scale offline. ```ts import { Sonzai } from "@sonzai-labs/agents"; const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! }); const JUDGE_AGENT = "agent_judge"; const SUBJECT_AGENT = "agent_subject"; const USER_ID = "eval_run_001"; const messages = [ { role: "user" as const, content: "I'm feeling really overwhelmed lately." }, ]; // Subject responds to the user prompt const subjectTurn = await client.agents.dialogue(SUBJECT_AGENT, { userId: USER_ID, messages, requestType: "eval_round", }); messages.push({ role: "assistant", content: subjectTurn.response }); // Judge scores the subject's response const judgeTurn = await client.agents.dialogue(JUDGE_AGENT, { userId: USER_ID, messages, sceneGuidance: "You are evaluating the previous assistant response for empathy and clarity. " + "Return a JSON object with keys: score (0–100), feedback (string).", requestType: "eval_judge", }); console.log("Subject:", subjectTurn.response); console.log("Judge verdict:", judgeTurn.response); // Then score the exchange through the evaluation API const evalResult = await client.agents.evaluate(SUBJECT_AGENT, { templateId: "empathy-rubric", messages, }); console.log("Eval score:", evalResult.score); ``` *** ## Tutorials [#tutorials] * [Medication Reminders](./tutorial-medication-reminders) — end-to-end example using Schedule + TriggerEvent + Memory together * [Scheduled Reminders walkthrough](./tutorial-scheduled-reminders) — covers cadence patterns that pair with events *** ## Next steps [#next-steps] * [Proactive Messaging](./proactive-overview) — the three sources and delivery channels * [Conversations](./conversations) — regular agent chat mechanics * [Scheduled Reminders](./scheduled-reminders) — recurring cadence primitive * [Evaluation](./evaluation) — scoring agent responses --- # Generation Source: https://sonz.ai/docs/en/generation > Generate agent characters, bios, avatars, and seed memories; generate images during chat. Generation covers two distinct capabilities: **agent generation** (spinning up a complete character — bio, personality, seed memories, avatar — from a text description) and **media generation** (producing images on demand during a chat turn). Both live under `client.agents.generation` and `client.agents.image`. ## What you can build with it [#what-you-can-build-with-it] * **One-click character creation** — spec a rough prompt, get a complete agent profile + avatar * **Avatar regeneration** — refresh an agent's look as the character evolves over time * **Seed memories** — plant backstory facts the agent "remembers" from day 1 * **In-chat image generation** — agent creates pictures as part of its responses * **Character templates** — generate many similar agents from a shared base prompt ## Quickstart [#quickstart] ### Generate a character + create the agent [#generate-a-character--create-the-agent] The single fastest path: one call generates a full personality profile and provisions the agent. Safe to call on every deploy — if the agent already exists, the LLM is skipped. If an `agentId` is provided and the agent already exists, `generateAndCreate` updates the existing agent rather than creating a duplicate. Safe to call on every app startup. ### Generate an image in-chat [#generate-an-image-in-chat] Call `client.agents.image.generate` with the agent ID and a prompt during or alongside a chat turn. ## Capabilities — image, music, and video generation [#capabilities--image-music-and-video-generation] Three `AgentCapabilities` flags gate media generation. Each flag is a boolean on the agent, and each has a paired `*UnlockedAt` timestamp that records when the capability was granted (typically via a tier upgrade or admin enable). Generation calls fail if the flag is `false`. | Field | Type | Description | | ----------------- | ------------------- | -------------------------------------------------- | | `imageGeneration` | `boolean` | Whether image generation is enabled for this agent | | `imageUnlockedAt` | `string (ISO 8601)` | When image generation was granted | | `musicGeneration` | `boolean` | Whether music/audio generation is enabled | | `musicUnlockedAt` | `string (ISO 8601)` | When music generation was granted | | `videoGeneration` | `boolean` | Whether video generation is enabled | | `videoUnlockedAt` | `string (ISO 8601)` | When video generation was granted | `imageGeneration` is the only media flag you can toggle directly via `update_capabilities()`. `musicGeneration` and `videoGeneration` are platform-managed — they flip when your plan includes those capabilities. Use `get_capabilities()` to inspect their current state. ## Core concepts [#core-concepts] ### Character generation [#character-generation] Character generation takes a natural-language description and produces a structured agent profile. You can generate the profile and immediately create the agent (`GenerateAndCreate`), or generate the profile first for preview and commit only on user approval (`GenerateCharacter`). **Input:** name, description, optional `fields` filter, optional `gender`, optional LLM `provider`/`model` override, optional `regenerate` flag. **Output:** bio, personality prompt, Big5 scores, speech patterns, interests, dislikes, primary traits, dimensions, interaction preferences, behavioral traits, initial goals. The `Regenerate` flag forces a fresh generation even when a cached profile is found — useful for iteration flows where the user wants a different result without deleting the agent. ```go // Preview without committing profile, err := client.Agents.Generation.GenerateCharacter(ctx, sonzai.GenerateCharacterOptions{ Name: "Atlas", Description: "A stoic, wise mentor who speaks in metaphors and values patience above all.", Fields: []string{"big5", "dimensions", "preferences", "behaviors"}, Regenerate: true, // force a fresh pass }) ``` Bio generation (`GenerateBio`) and avatar regeneration (`RegenerateAvatar`) are narrower variants — they update a single attribute of an existing agent without touching the rest of the profile. Seed memories work in two steps: 1. **Generate** — `GenerateSeedMemories` calls an LLM to produce backstory memories from the agent's personality, interests, and lore context. 2. **Store** — `SeedMemories` bulk-imports a list of memory objects (generated or hand-authored) into the agent's memory store. You can run both steps separately for fine-grained control, or set `storeMemories: true` on the generate call to do both in one request. ### Media generation [#media-generation] Image generation is agent-scoped: `client.agents.image.generate(agentID, opts)`. The agent ID is used to apply the agent's visual style and context to the generation request. **Input:** `prompt` (required), optional `negative_prompt`, optional `model`/`provider` override, optional `output_bucket`/`output_path` for custom storage routing. **Output:** `image_id`, `public_url`, `mime_type`, `generation_time_ms`. Images are generated synchronously — the call blocks until the image is ready and returns a public CDN URL. For high-throughput workflows, fan out parallel calls rather than queuing. ## Full API [#full-api] | Method | Returns | Description | | ------------------------------------------------ | ------------------------------ | --------------------------------------------------------------- | | `generation.generateAndCreate(opts)` | `GenerateAndCreateResponse` | Generate character + create agent in one idempotent call | | `generation.generateCharacter(opts)` | `GenerateCharacterResponse` | Generate character profile without creating the agent | | `generation.generateBio(agentID, opts)` | `GenerateBioResponse` | Generate or regenerate bio for an existing agent | | `generation.generateSeedMemories(agentID, opts)` | `GenerateSeedMemoriesResponse` | Generate LLM-authored backstory memories | | `generation.seedMemories(agentID, opts)` | `SeedMemoriesResponse` | Bulk-import pre-authored memories into the agent's memory store | | `generation.regenerateAvatar(agentID, opts)` | `RegenerateAvatarResponse` | Regenerate the agent's avatar image | | `image.generate(agentID, opts)` | `ImageGenerateResponse` | Generate an image from a prompt, scoped to the agent | ## Combines with other features [#combines-with-other-features] ### With [Personality](./personality) — character generation sets initial Big5 [#with-personality--character-generation-sets-initial-big5] `generateCharacter` (and `generateAndCreate`) returns a full Big5 profile derived from the description. The platform uses these scores directly as the agent's personality baseline — no manual `personality.update` call needed. ### With [Memory](./memory) — seed memories plant backstory [#with-memory--seed-memories-plant-backstory] Generate memories from the agent's personality context, then seed them into the memory store. They appear immediately in `memory.list` and are recalled in the agent's first conversations. ### With [Conversations](./conversations) — image generation as a chat capability [#with-conversations--image-generation-as-a-chat-capability] Call `image.generate` within a chat turn to let the agent produce images as part of its response. Render the returned URL alongside the text content. ## In Practice [#in-practice] **Generation is a one-off setup tool.** Define your agent once via `generateAndCreate` or `create`, pin the Big5 config in your environment, and don't regenerate in production. Predictability matters more than novelty. **Version the generated profile.** Store the exact personality config in production at any point in time so compliance can replay behavior during audits. **Don't expose generation to end-users.** End-customers should never be able to regenerate an enterprise agent's personality — that opens brand-voice risk. **Image generation is opt-in per integration.** Only enable `image.generate` in workflows where you have reviewed and approved the output pipeline. Apply content filters via `negative_prompt` to enforce brand safety. ## Tutorials [#tutorials] * [Tutorial: Seed memories from scratch](/docs/en/guides/tutorials/tutorial-memory) ## Next steps [#next-steps] * [Personality](./personality) — understand the Big5 profile that generation produces * [Memory](./memory) — how seed memories integrate with the live memory system * [Conversations](./conversations) — wiring image generation into a chat turn --- # Sonzai Relationship Layer Source: https://sonz.ai/docs/en/home > Persistent memory, personality, and context for AI agents — whether you're building an assistant, a character, or an enterprise workflow agent. Sonzai is the **Relationship Layer** for AI agents: a hosted platform that gives any agent persistent memory, evolving personality, mood, relationships, and a knowledge graph. Integrate via REST, MCP, or native SDKs for TypeScript, Python, and Go. ## Install [#install] Pick the path that matches your stack. All paths talk to the same hosted API — mix and match freely (e.g. backend in Python, plus MCP from Claude Desktop for ops). * **Python** 3.11+. Sync (`Sonzai`) and async (`AsyncSonzai`) clients ship in the same package. * **TypeScript** runs on Node.js >=18, Bun, and Deno. Zero runtime dependencies. * **Go** 1.25+. Standard library only. * All SDKs read `SONZAI_API_KEY` from the environment by default. * **OpenClaw** itself is required for the OpenClaw path — install it from [openclaw.ai](https://openclaw.ai) ([Getting Started](https://docs.openclaw.ai/start/getting-started)). * **Hermes Agent** itself is required for the Hermes path — install it from [hermes-agent.nousresearch.com](https://hermes-agent.nousresearch.com). * Full guides: [MCP](/docs/en/guides/mcp-integration) · [OpenClaw](/docs/en/guides/openclaw-integration) · [Hermes](/docs/en/connect-hermes) · [REST API Reference](/docs/en/reference/api-reference). Need an API key first? Create a project at [platform.sonz.ai](https://platform.sonz.ai/dashboard/projects), then jump to the [Quickstart](/docs/en/guides/getting-started) for the full walkthrough. ## What are you building? [#what-are-you-building] Pick the track that matches your product. Each quickstart walks through the features that matter for your use case — and explicitly flags what you can skip. ## Core Capabilities [#core-capabilities] Every feature below works for all three audiences, but the emphasis differs. Use the **In practice** tabs on each page to jump to examples for your use case. ## Integration [#integration] ## For AI Agents [#for-ai-agents] Feeding Sonzai docs to an AI assistant? Every page has a **Copy for LLM** button, and the bundles below are pre-formatted for ingestion. Each page also has a raw-markdown URL: append `.md` to any doc path. For example, `/docs/en/memory.md` returns plain markdown ready to paste into an LLM or pipe into a tool. ## How It Fits Into Your Application [#how-it-fits-into-your-application] Your backend handles business logic and user sessions. The Relationship Layer owns agent intelligence — personality, memory, mood, and relationships. Connect via REST, MCP, or SDK; pass application context per request; let the platform manage everything else. [Read the full integration guide →](/docs/en/guides/integration) --- # Instances Source: https://sonz.ai/docs/en/instances > Deploy the same agent into multiple isolated contexts — workspaces, departments, regions, or tenants — each with their own independent state. **Instances** let you run a single agent across many isolated deployment contexts without cloning the agent itself. The shared parts — [personality](./personality), [memory](./memory), tools, voice — stay unified, while [custom states](./custom-states) are scoped per instance so a US-East workspace, an EU-West tenant, and a staging environment never see each other's data. Every agent gets a `default` instance for free; you only need explicit instances when the same AI agent runs in parallel contexts that must not share runtime state. ## What is an Instance? [#what-is-an-instance] An `Instance` is a deployment context for an agent. The agent itself (personality, memory, tools) is shared — but custom state is isolated per instance. {` Agent "Luna" ├── Instance: default ← used when instanceId is omitted ├── Instance: ws-us-east ← US-East workspace ├── Instance: ws-eu-west ← EU-West workspace └── Instance: ws-staging ← separate deployment Each instance has its own: • Global custom states (environment state, configuration) • Per-user custom states scoped to this instance • Isolated from other instances`} Every agent has a default instance. If you don't pass `instanceId` to chat or state operations, the default instance is used. You only need multiple instances if you run the same agent in parallel isolated contexts. ## List Instances [#list-instances] ## Create an Instance [#create-an-instance] ## Get an Instance [#get-an-instance] ## Update an Instance [#update-an-instance] ## Reset an Instance [#reset-an-instance] Clears all custom state data for an instance without deleting it. Useful for resetting an environment between sessions. ## Delete an Instance [#delete-an-instance] ## Using Instances in Chat [#using-instances-in-chat] Pass `instanceId` to chat calls to scope state reads to that instance. The agent will see global custom states for that instance and per-user states scoped to it. ## Instance Data Model [#instance-data-model] * `instanceId` (`string`): Unique instance identifier * `agentId` (`string`): Parent agent ID * `name` (`string`): Human-readable label * `description` (`string?`): Optional description * `status` (`string`): "active" or "inactive" * `isDefault` (`boolean`): True for the auto-created default instance * `createdAt` (`string`): ISO 8601 timestamp * `updatedAt` (`string`): ISO 8601 timestamp --- # Inventory Source: https://sonz.ai/docs/en/inventory > Per-user structured items the agent can read and reason about — medications, holdings, pets, goals — stored against a shared KB schema. Inventory is the place to store structured per-user data the agent should know about. Each item belongs to a single `agent × user` pair and follows a schema defined in your [Knowledge Base](./knowledge-base), so the agent always has typed, queryable data rather than free-form text. When the agent adds an item it searches the KB by description to resolve and link the right node automatically. ## What you can build with it [#what-you-can-build-with-it] * **Medication adherence** — track each drug, dose, and schedule per user (pairs with [Scheduled Reminders](./scheduled-reminders)) * **Portfolio / holdings** — stocks, crypto, collectibles with market-value joins via the KB * **Pet care** — pets per user, feeding, vet, and growth tracking * **Goal tracking** — user-defined goals with progress state * **Plants / hobbies** — anything that follows a "user has N things of type T" pattern ## Quickstart [#quickstart] Add a medication to a user's inventory. The response includes an `inventory_item_id` (and the backward-compatible `fact_id` alias) you can use for direct updates or deletes later. When `action` is `"add"`, the platform performs a natural-language search of the KB using `description`. If exactly one node matches, the item is linked automatically and the response includes `kb_resolution`. If there are multiple close matches, the response returns `status: "disambiguation_needed"` and a `candidates` list — surface these to the user or pick the best `kb_node_id` and re-submit. `label` is an optional short display name shown in dashboards and agent tool calls (e.g. `"Metformin"`). `description` is the longer text the platform uses for KB natural-language search (e.g. `"Metformin 500mg — biguanide for blood sugar control"`). If `label` is omitted, the platform falls back to the first segment of `description` for display purposes. ## Core concepts [#core-concepts] * **Items belong to users** — every item is scoped to `agent_id × user_id`; no item is shared across users * **Schema-driven shape** — `item_type` references a [KB schema](./knowledge-base) that defines the valid property fields; the platform validates writes against it * **Two write paths for adding items** — use `inventory.create({...})` (dedicated route, no action field) for cleaner code when you specifically want to add; use `inventory.update({action: "add", ...})` (explicit-action route) when you handle add/update/remove through a single call site. Both hit equivalent server logic. * **`label` vs `description`** — `label` is a short display name for dashboards and agent UI (e.g. `"Ibuprofen"`); `description` is the longer text the KB search uses to resolve the right node (e.g. `"anti-inflammatory pain reliever, 400mg"`). Both are optional but providing both gives the clearest results. * **KB resolution** — on add, Sonzai searches the KB by `description`; on ambiguous matches it returns `candidates` and `status: "disambiguation_needed"` so you can resolve before committing * **Query modes** — `"list"` returns raw items, `"value"` joins with live KB market data and computes `gain_loss`, `"aggregate"` returns totals and grouped sums without listing every item ## Full API [#full-api] | Method | Description | | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `inventory.create(agentId, userId, { item_type, label?, description?, kb_node_id?, properties?, project_id? })` | **Preferred add path.** Dedicated create endpoint — no `action` field needed. Returns `InventoryUpdateResponse` with `inventory_item_id`. | | `inventory.update(agentId, userId, { action, item_type, label?, description?, kb_node_id?, properties?, project_id? })` | Explicit-action path. `action` is `"add"`, `"update"`, or `"remove"`. Use when you route all three write types through one call site. | | `inventory.query(agentId, userId, { mode, item_type?, project_id?, filters?, sort_by?, sort_order?, aggregations?, limit?, offset?, cursor? })` | Query items in list, value, or aggregate mode. | | `inventory.directUpdate(agentId, userId, factId, { properties })` | Update an item's properties by `fact_id`, bypassing KB re-resolution. | | `inventory.directDelete(agentId, userId, factId)` | Delete an item by `fact_id`. | | `inventory.batchImport(agentId, userId, { items: [{ item_type, description?, kb_node_id?, properties? }], project_id? })` | Import up to 1,000 items in one call. | ### Response shape [#response-shape] `InventoryUpdateResponse`: ```json { "status": "added", "inventory_item_id": "inv_01HX...", "fact_id": "fact_01HX...", "kb_resolution": { "resolved": true, "kb_node_id": "node_xyz", "kb_label": "Metformin 500mg", "kb_properties": { "drug_class": "biguanide" } } } ``` `inventory_item_id` is the preferred identifier going forward. `fact_id` is included for backward compatibility — both refer to the same item and are interchangeable in all subsequent API calls (direct update, direct delete, schedule linkage). When `status` is `"disambiguation_needed"`, the response includes a `candidates` array instead of `kb_resolution`. Re-submit with the chosen `kb_node_id` set explicitly to bypass the search. ## Combines with other features [#combines-with-other-features] ### With [Knowledge Base](./knowledge-base) — schemas shape items [#with-knowledge-base--schemas-shape-items] The `item_type` field points to a KB entity schema that defines which properties are valid for that type. Create the schema once; all inventory writes for that type are validated against it. ### With [Scheduled Reminders](./scheduled-reminders) — live data at fire time [#with-scheduled-reminders--live-data-at-fire-time] A schedule can reference an `inventory_item_id`. At each fire, the agent reads the item's current properties rather than a snapshot baked into the schedule definition. Updating the item's dosage automatically flows to the next reminder without touching the schedule itself. ### With [Memory](./memory) — inventory state in conversation context [#with-memory--inventory-state-in-conversation-context] During a conversation the agent can query the user's inventory to answer questions like "what medications am I taking?" directly. Inventory writes also generate memory facts that surface in future sessions, so the agent can reference holdings and items across conversations without a manual query. ## Tutorials [#tutorials] * [Resource Inventory + Knowledge Base](./tutorial-inventory) — full walkthrough with schema setup, upsert, bulk import, and portfolio queries * [Medication Reminders](./tutorial-medication-reminders) — worked example combining Inventory + Scheduled Reminders + Memory ## Next steps [#next-steps] * [Knowledge Base](./knowledge-base) — the schema backbone that shapes inventory items * [Scheduled Reminders](./scheduled-reminders) — live inventory data injection at fire time * [Memory](./memory) — how inventory writes surface in chat context --- # Knowledge Analytics Source: https://sonz.ai/docs/en/knowledge-analytics > Turn the Knowledge Base graph into a recommendation engine — rules rank nodes by user affinity, trend velocity, or conversion rate, all readable through a simple query-time API. Knowledge Analytics layers a ranking system on top of the Knowledge Base. Rules define scoring signals — per-user affinity for recommendations, aggregate velocity for trends — and readers fetch ranked results at query time with a single call. The graph backbone supplies the nodes and edges; analytics rules decide how to score and order them. The result is a reusable ranking layer that powers product recommendations, trending dashboards, and conversion tracking without building a separate data pipeline. ## What you can build with it [#what-you-can-build-with-it] * **Product recommendations** — "top-N products for this user" based on user affinity signals * **Trending topics** — "what's rising this week across all users" via aggregate velocity scoring * **Conversion dashboards** — which KB nodes convert (browse to engage to buy) and at what rate * **Per-segment ranking** — different recommendation models for different user segments * **Feedback loops** — record converted recommendations to continuously sharpen scoring over time ## Quickstart [#quickstart] Create a recommendation rule, fetch ranked results for a user, then record whether the user acted on a recommendation. ## Core concepts [#core-concepts] * **Rule types** — `"recommendation"` scores nodes per source (e.g. per user), returning a personalised top-N list. `"trend"` aggregates signals across all sources, returning global velocity rankings. * **Config is rule-specific** — the `config` object is a passthrough shape; its fields depend on the rule type and your scoring model. There is no fixed schema enforced by the SDK — pass whatever your rule implementation expects (e.g. `target_entity_type`, `scoring`, `decay_factor`). * **Source and target semantics** — recommendations take a `source_id` (typically a user node ID) and return ranked nodes of the target entity type. The source must exist as a node in the Knowledge Base graph. * **Scheduled vs manual** — rules can carry an optional cron `schedule` for batch recomputation (e.g. `"0 * * * *"` for hourly). Call `RunAnalyticsRule` at any time to trigger a manual run outside the schedule. * **Feedback closes the loop** — `RecordFeedback` writes a signal back against the source, target, and rule. Subsequent recomputation can weight nodes that historically converted higher, sharpening ranking over time. Use the `action` field to record fine-grained user intent: `"converted"` (user completed the action), `"clicked"` (user opened the recommendation), `"dismissed"` (user explicitly rejected it), or `"ignored"` (recommendation was shown but user did not interact). `action: "converted"` sets `converted: true` automatically so existing aggregate conversion queries continue to work without changes. ## Full API [#full-api] | Method | Returns | Description | | ----------------------------------------------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `createAnalyticsRule(projectId, { rule_type, name, config, enabled, schedule? })` | `KBAnalyticsRule` | Create a new analytics rule. `rule_type` is `"recommendation"` or `"trend"`. | | `listAnalyticsRules(projectId)` | `KBAnalyticsRuleListResponse` | List all analytics rules for a project. | | `getAnalyticsRule(projectId, ruleId)` | `KBAnalyticsRule` | Fetch a single rule by ID. | | `updateAnalyticsRule(projectId, ruleId, { name?, config?, enabled, schedule? })` | `KBAnalyticsRule` | Update rule properties. | | `deleteAnalyticsRule(projectId, ruleId)` | `void` | Delete a rule permanently. | | `runAnalyticsRule(projectId, ruleId)` | `void` | Trigger an immediate manual run of the rule. | | `getRecommendations(projectId, ruleId, sourceId, limit?)` | `KBRecommendationsResponse` | Fetch ranked recommendations for a source node. Returns `.recommendations` array of `{ target_id, target_type, score }`. | | `getTrends(projectId, nodeId)` | `KBTrendsResponse` | Get trend aggregations for a specific node across all windows. Returns `.trends` array of `{ node_id, rule_id, window, value, direction }`. | | `getTrendRankings(projectId, ruleId, rankingType, window, limit?)` | `KBTrendRankingsResponse` | Get the global leaderboard for a trend rule. `window` is a duration string such as `"7d"` or `"24h"`. Returns `.rankings` array with `rank` and `score`. | | `getConversions(projectId, ruleId, segment?)` | `KBConversionsResponse` | Fetch conversion statistics for a rule, optionally filtered by segment key. Returns `{ shown_count, conversion_count, conversion_rate }` per segment. | | `recordFeedback(projectId, { source_node_id, target_node_id, rule_id, converted, score_at_time, action? })` | `void` | Record whether a recommended node was acted on. `converted` is a boolean — `true` means the user engaged with the recommendation. `action` is an optional string enum: `"converted"`, `"dismissed"`, `"clicked"`, `"ignored"`. Passing `action: "converted"` also sets `converted: true` for backward-compatible aggregate queries. | | `getStats(projectId)` | `KBStats` | General KB statistics (node counts, document counts, extraction tokens). | The Python SDK exposes `get_recommendations`, `get_trends`, `get_trend_rankings`, `get_conversions`, and `record_feedback` using keyword-only arguments after `project_id`. For example: `client.knowledge.get_recommendations(project_id, rule_id="...", source_id="...", limit=10)`. ## Combines with other features [#combines-with-other-features] ### With [Knowledge Base](./knowledge-base) — the graph is the substrate [#with-knowledge-base--the-graph-is-the-substrate] Analytics rules run over KB nodes and edges. Entity schemas define what types of nodes exist; rules score those nodes. The recommended pattern is to define your entity schema first, then create rules that target it. ### With [Inventory](./inventory) — per-user holdings drive per-user recommendations [#with-inventory--per-user-holdings-drive-per-user-recommendations] Inventory writes create edges from a user node to the nodes they own. Those ownership edges flow into the recommendation model as affinity signals: items a user already owns inform which related nodes score highest. ### With [Agent Insights](./agent-insights) — conversation signals sharpen rankings [#with-agent-insights--conversation-signals-sharpen-rankings] Agent Insights extract what users express interest in during conversations. Those interest signals can be passed into recommendation rule config as additional affinity weights, so a user who talks about budget peripherals gets different rankings than one who discusses high-end setups — without any explicit user input. ## Tutorials [#tutorials] No dedicated Knowledge Analytics tutorial exists yet. The [Knowledge Base tutorial](./knowledge-base) covers schema setup and fact insertion — the prerequisite steps before creating analytics rules. ## Next steps [#next-steps] * [Knowledge Base](./knowledge-base) — the graph backbone; define schemas and push nodes before creating rules * [Inventory](./inventory) — per-user holdings create user-to-node edges that feed the recommendation model * [Organization Knowledge Base](./organization-knowledge-base) — analytics rules can also run over org-scoped KB nodes for shared ranking across all users --- # Knowledge Base Source: https://sonz.ai/docs/en/knowledge-base > Store and search structured facts, documents, and entity graphs your AI agents query during conversations — and that they can write back into themselves, forming a closed-loop multiplayer memory shared across every agent in the project. The Knowledge Base gives your agents a live, searchable store of facts and documents — so they answer from real data instead of guessing. You push data in (via file upload or API), the platform builds a knowledge graph, and agents query it in real time. Schemas are the bridge to the [Inventory](./inventory) primitive: the same entity types you define here back every per-user inventory item, letting a single schema serve both global knowledge and user-specific state. It is also **multiplayer**. Agents can autonomously write what they learn during conversations back into the project KB, where every other agent on the project reads it on the next session — a closed-loop **company brain** that compounds the way human institutional memory does. And a single agent serving a team can carry attributed memory *across users*, so it can inform user A with the context it gathered while talking to user B. See [Multiplayer memory](#multiplayer-memory-a-company-brain) below. ## How knowledge gets into the KB [#how-knowledge-gets-into-the-kb] There are **two ways to populate the knowledge base**, plus **one optional capability** you toggle on top of either of them: **1. Manual upload.** Drop in a PDF, DOCX, Markdown, or plain text file via the SDK or the dashboard. The platform extracts entities and relationships automatically and writes them to the graph. Use this for static documents you control — handbooks, policies, product manuals, lore. One-shot, or re-uploaded whenever the source changes. → [Upload a document](#upload-a-document) **2. ETL job that pushes on delta changes.** Define an entity schema once; have your job call `insertFacts` or `bulkUpdate` on a schedule, queue, or change-data-capture stream. Use this for live upstream sources of truth — databases, price feeds, CMSes, scrapers — so the KB stays in sync as the source changes. Upserts are idempotent; pushing the same label twice merges properties and increments the version, so the same job is safe to re-run on any cadence. → [Define a schema, then push facts](#define-a-schema-then-push-facts) **+ Autonomous agent editing** *(optional toggle — enable or disable per agent or project-wide).* Flip the `knowledgeBaseWrite` capability on and agents get `knowledge_create / knowledge_update / knowledge_delete` tools. During conversations they record verified facts themselves, with a full audit trail (each write is stamped `source = "agent:"`) and compare-and-swap update semantics so concurrent admin edits never get clobbered. Use this when the source of truth IS the conversation — support agents recording verified incident details, customer-success agents capturing renewal context, scribe agents writing meeting notes. → [Agents writing to the knowledge base](#agents-writing-to-the-knowledge-base) The two ingestion paths are independent — pick either, both, or neither. Autonomous editing is a per-agent toggle (or a project-wide default via `default_agent_kb_write`) that sits *on top of* whichever ingestion paths you're already running. You stay in control: every agent write is server-side validated against your schema, capped by quotas, scoped to the agent's own project, and reversible — soft-delete only, hard delete stays admin-only. {` Manual upload ETL on delta changes Agent in conversation (PDF / DOCX / MD) (insertFacts / bulkUpdate) (knowledge_create / update / delete) | | | | | | requires | | | knowledgeBaseWrite: true v v v +----------------------------------------------------------------+ | Project Knowledge Graph | | entities + relationships + version history + audit trail | +----------------------------------------------------------------+ | v Agents read via knowledge_search during every conversation `} ## What you can build with it [#what-you-can-build-with-it] * **Real-time product Q\&A** — push a live product catalog and let agents answer "what's in stock under $50?" with current prices and availability * **Medication or supplement advisor** — store drug and dosage facts; the agent surfaces the right information when a user asks about interactions or timing * **Collectibles price tracker** — scrape market prices hourly, push via `bulkUpdate`, and let agents answer "what's trending up this week?" with real data * **Internal knowledge assistant** — upload employee handbooks, policy docs, and product manuals; agents ground answers in authoritative sources instead of hallucinating * **Personalized recommender** — define recommendation rules on entity fields (set, rarity, budget) and surface the top matches for each user at conversation time ## Quickstart [#quickstart] ### Upload a document [#upload-a-document] Upload a PDF, DOCX, Markdown, or plain text file. The platform extracts entities and relationships automatically. ### Define a schema, then push facts [#define-a-schema-then-push-facts] Define typed fields for an entity type, then insert structured facts. Good for scrapers, inventory feeds, and price trackers. ## Core concepts [#core-concepts] ### Knowledge graph [#knowledge-graph] Entities are nodes; relationships are typed edges. Nodes deduplicate by normalized label + type — pushing the same label twice merges properties and increments the version. Every change is recorded in version history with source and timestamp, giving you a full audit trail. The graph is completely domain-agnostic: you define entity types and relationship types; the platform stores and indexes them. ### Similarity edges [#similarity-edges] When a schema has a `similarity_config`, the platform automatically creates `similar_to` edges between entities whose `match_fields` values are close enough to exceed the `threshold`. This turns structured fields into graph topology without any extra work — and powers the recommendation engine. ### Entity type naming: `entity_type` vs `display_name` [#entity-type-naming-entity_type-vs-display_name] `entity_type` is the machine-readable slug used everywhere in the API (e.g. `"pokemon_card"`). It is how inventory writes and KB lookups reference the schema. `display_name` is the optional human-friendly label shown in the dashboard and agent tool descriptions (e.g. `"Pokémon Card"`). If `display_name` is omitted, the dashboard falls back to a title-cased version of `entity_type`. ### Controlling BM25 indexing per field [#controlling-bm25-indexing-per-field] By default every field value is included in the BM25 full-text index so agents can find nodes by searching field contents. Set `indexed: false` on a field to exclude it from the search index — the value is still stored and returned in reads, but it will not match keyword queries. Use this for fields that should be readable but not searchable, for example: * Internal identifiers (`sku`, `barcode`, `external_id`) that should never surface in agent search results * High-cardinality numeric values like dosage amounts on a medication schema, where token matching produces noise rather than signal * Raw HTML or markdown blobs that you render in UI but do not want polluting search ```typescript fields: [ { name: "name", type: "string", required: true }, // searchable { name: "dosage", type: "string", indexed: false }, // stored-only { name: "sku", type: "string", indexed: false }, // stored-only ] ``` ### Upsert semantics [#upsert-semantics] `insertFacts` and `bulkUpdate` default to upsert mode (`upsert: true`): if a node with the same label + type exists, its properties are merged and the version is incremented; if it does not exist, it is created. This makes idempotent syncs safe to run on any schedule. Set `upsert: false` for strict update-only semantics: nodes that do not already exist are skipped rather than created, and their IDs appear in the response `not_found` list. Use this when you want to ensure you are only patching existing data and never accidentally inserting stale or erroneous entries from an upstream feed. ### How agents use the graph [#how-agents-use-the-graph] During conversations, agents have access to a `knowledge_search` tool that queries your graph. Instead of hallucinating facts, the agent calls this tool and returns grounded answers. The search result includes the entity's properties, relevance score, and any related nodes reachable via one-hop traversal. ## Full API [#full-api] All SDK methods are on `client.knowledge.*` (TS/Python) or `client.Knowledge` (Go). ### Documents [#documents] | Method | Returns | Description | | ---------------------------------- | ------------ | --------------------------------------------- | | `uploadDocument(projectId, opts)` | `Document` | Upload a file for automatic entity extraction | | `listDocuments(projectId)` | `Document[]` | List documents and their processing status | | `deleteDocument(projectId, docId)` | `void` | Delete a document and its extracted entities | ### Schemas [#schemas] | Method | Returns | Description | | ----------------------------------------- | ---------- | ------------------------------------------------------------ | | `createSchema(projectId, opts)` | `Schema` | Define a typed entity schema with optional similarity config | | `listSchemas(projectId)` | `Schema[]` | List all schemas for the project | | `updateSchema(projectId, schemaId, opts)` | `Schema` | Update fields or similarity config | ### Facts and graph [#facts-and-graph] | Method | Returns | Description | | ------------------------------ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `insertFacts(projectId, opts)` | `InsertResult` | Upsert entities and relationships | | `bulkUpdate(projectId, opts)` | `void` | Patch properties on many nodes at once; only changed fields are written. Pass `upsert: false` for strict update-only semantics (missing nodes are returned as `not_found` instead of being created). | | `listNodes(projectId, opts?)` | `Node[]` | List nodes, optionally filtered by entity type | | `getNode(projectId, nodeId)` | `Node` | Fetch a single node with its edges and version history | ### Search [#search] | Method | Returns | Description | | ------------------------- | -------------- | -------------------------------------------------------------------- | | `search(projectId, opts)` | `SearchResult` | Full-text search with type filter, property filters, and graph depth | ### Analytics [#analytics] | Method | Returns | Description | | ---------------------------------------------------------- | ---------------------- | ------------------------------------------------------ | | `createAnalyticsRule(projectId, opts)` | `AnalyticsRule` | Create a recommendation or trend rule | | `listAnalyticsRules(projectId)` | `AnalyticsRule[]` | List all rules | | `runAnalyticsRule(projectId, ruleId)` | `void` | Trigger a rule run immediately | | `getRecommendations(projectId, opts)` | `RecommendationResult` | Fetch pre-computed recommendations for a source node | | `getTrendRankings(projectId, ruleId, type, window, limit)` | `TrendRankings` | Top gainers, losers, most volatile, or highest average | | `recordFeedback(projectId, opts)` | `void` | Record whether a recommendation converted | ## Combines with other features [#combines-with-other-features] ### With [Inventory](./inventory) — shared schemas for per-user items [#with-inventory--shared-schemas-for-per-user-items] Inventory items are knowledge graph nodes scoped to a specific user. The same `entity_type` you define in a KB schema can back both global knowledge entries and per-user inventory items, so the agent reasons across both surfaces with a single mental model. When you call `inventory.update` with `action: "add"`, the platform creates a node in the graph and returns a `fact_id` — the same identifier you use in KB lookups. ```ts // Add a per-user inventory item that lives in the knowledge graph const item = await client.agents.inventory.update("agent_abc", "user_123", { action: "add", item_type: "medication", description: "Ibuprofen 500mg", project_id: "proj_abc", properties: { medication_name: "ibuprofen", dosage: "500mg", frequency: "twice daily", }, }); // item.fact_id is a knowledge graph node ID — use it for KB lookups or schedule linkage console.log(item.fact_id); ``` ### With [Scheduled Reminders](./scheduled-reminders) — live data injection at fire time [#with-scheduled-reminders--live-data-injection-at-fire-time] A schedule can reference an `inventory_item_id` (a `fact_id` from the graph). At every fire the platform reads the item's **current** properties from the knowledge graph and injects them into the agent's wakeup block. This means a dosage change or property update flows through to the next reminder with no schedule edit required — the graph is the single source of truth for what the reminder is about. ```ts // 1. Create the inventory item (returns fact_id) const item = await client.agents.inventory.update("agent_abc", "user_123", { action: "add", item_type: "medication", description: "Ibuprofen", project_id: "proj_abc", properties: { medication_name: "ibuprofen", dosage: "500mg" }, }); // 2. Link the schedule — at every fire the graph is re-read for live properties await client.schedules.create("agent_abc", "user_123", { cadence: { simple: { frequency: "daily", times: ["08:00", "20:00"] }, timezone: "Asia/Singapore", }, intent: "remind the user to take their ibuprofen at the correct dose", check_type: "reminder", inventory_item_id: item.fact_id, }); ``` ### With [Knowledge Analytics](./knowledge-analytics) — graph becomes a recommender [#with-knowledge-analytics--graph-becomes-a-recommender] Define analytics rules on your entity graph to surface recommendations and trend rankings. Rules match source entities to target entities by field similarity, price range, or other numeric proximity. Conversion feedback flows back into the rule to improve rankings over time. The same graph you use for search becomes a live recommender with no extra data store. ```ts // Create a recommendation rule matching cards by set and rarity const rule = await client.knowledge.createAnalyticsRule(projectId, { rule_type: "recommendation", name: "Similar cards", config: { match_fields: ["set", "rarity"], limit: 5 }, enabled: true, }); // Fetch pre-computed recommendations for a source node const recs = await client.knowledge.getRecommendations(projectId, { rule_id: rule.rule_id, source_id: sourceNodeId, limit: 5, }); for (const rec of recs.recommendations) { console.log(rec.target_id, rec.score); } // Record conversion feedback — improves future rankings await client.knowledge.recordFeedback(projectId, { rule_id: rule.rule_id, source_node_id: sourceNodeId, target_node_id: recs.recommendations[0].target_id, converted: true, score_at_time: recs.recommendations[0].score, }); ``` ## Multiplayer memory: a company brain [#multiplayer-memory-a-company-brain] Sonzai's knowledge layer is not a static store you hand-curate and agents read from once. It is a closed-loop system your agents read, write to, and learn from collaboratively — the way a real team builds shared institutional memory. Three capabilities stack on top of each other: | Layer | What it does | Default | Where it lives | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------- | | **Read** | Every agent grounds its replies in the project KB and (optionally) the org-scope KB. | On for any agent with `knowledgeBase: true`. | Per-project + organisation-wide. | | **Write — autonomous** | Agents create, update, and soft-delete project KB entries themselves during conversations. Audit trail stamps which agent made which change. | Off until `knowledgeBaseWrite: true`. | Per-project; capability-gated. | | **Share across users** | A single agent serving a team carries attributed memory across users — `wisdom` (de-attributed, on by default) plus `sharedMemory` (attributed, opt-in). | `wisdom` on; `sharedMemory` off. | Per-agent; capability-gated. | The result is the same compounding effect human teams get from institutional knowledge: an agent doesn't just remember what *it* did with one user — it picks up what *the team* did, and a new agent joining the project benefits from everything every previous agent already wrote down. {` Project (your tenant) | +--------------------------+--------------------------+ | | | v v v agent A agent B agent C | | | |--- writes verified ------+ | | incident detail | | | | | | reads + grounds reply | | | | | +--- updates the entry --->| | | | reads enriched fact v v v user X user Y user Z Inter-agent: closed loop. Anything one agent learns is instantly available to every other agent on the project. Intra-agent: a single agent can also share memory across the users it serves -- attributed (sharedMemory) or de-attributed (wisdom). Same agent, multiple users, shared context. `} Real-world shapes this enables: * **Customer-success scribes.** Agent A captures verified renewal context with user X; agent B picks it up on a follow-up call with the same account. * **Support that learns from itself.** Each verified incident detail an agent records is grounded data for every other agent the next time the same product issue surfaces. * **Team coordinators.** One agent serves the whole project team — "Alice owns the migration, Bob is on incident response" — and informs each teammate with the context it gathered with the others. * **Group / party planning.** "Carol brings dessert, Dave does setup." Everyone joining the agent already knows who's doing what. * **Cross-product company brain.** [Organization-scope KB](./organization-knowledge-base) sits above projects: tenant-wide policies, lore, brand, and reference catalogs every project agent reads alongside its own. The detailed mechanics of each layer are below. ## Agents writing to the knowledge base [#agents-writing-to-the-knowledge-base] By default the KB is admin-curated: you push data in via document upload or the `bulkUpdate` API, and agents only read. You can **opt agents into autonomous editing** so they create, update, and soft-delete entries themselves during conversations — useful when the source of truth is the conversation (e.g. a customer-success agent capturing renewal context, or a support agent recording verified incident details). ### Three tools the agent gets when this is on [#three-tools-the-agent-gets-when-this-is-on] | Tool | What the agent can do | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `knowledge_create` | Insert a new node into the project KB with typed properties. | | `knowledge_update` | Patch existing properties using compare-and-swap — the agent first reads, then submits the version it saw, so concurrent admin edits never get clobbered silently. | | `knowledge_delete` | Soft-delete a node (`is_active = false`). Soft only; hard delete stays admin-only. | Every write is stamped with `source = "agent:"` on each `PropertySource`, so the KB audit trail shows exactly which agent made which change. Schema validation, write quotas, and the project-tenant scope check all run server-side — capability-on agents can only touch their own project. ### Two ways to turn it on [#two-ways-to-turn-it-on] **Per-agent** — set `knowledgeBaseWrite: true` on the agent's capabilities. Most useful when only specific agents in a project should be allowed to edit (e.g. a "scribe" agent vs. a customer-facing one). ```ts await client.agents.updateCapabilities("agent_abc", { knowledgeBase: true, // required prerequisite — agent must be able to read first knowledgeBaseWrite: true, }); ``` **Project default** — flip the project's `default_agent_kb_write` toggle. Every agent in that project with `knowledgeBase: true` gets the write capability automatically. Available in the dashboard at `/dashboard/knowledge` (the toggle next to the project selector) and via the API: ```ts await client.projects.update(projectId, { default_agent_kb_write: true, }); ``` The platform resolves both flags with **OR semantics** — the agent's own flag wins immediately when on; the project default applies only when the agent flag is off. So you can default-on the whole project and not need to touch each agent. `knowledgeBaseWrite` requires `knowledgeBase: true` to also be on — an agent that can't read the KB can't intelligently edit it. The platform refuses to register the write tools when only write is enabled and logs a warning. ## Wisdom & shared memory [#wisdom--shared-memory] Shared memory has its own full documentation page — see [**Shared Memory**](./shared-memory) for when to use it, how to enable and disable it, what tools the agent gets, how to verify it's working with live API probes, and the full privacy-control story. The summary below is here so KB readers see the multiplayer-memory hook in context. Beyond static documents, agents that talk to many users develop **patterns** — recurring behaviours, common goals, stable preferences. Sonzai surfaces this cross-user generalization through two complementary tiers: **wisdom** (de-attributed, on by default) and **shared memory** (attributed, opt-in). ### Wisdom (de-attributed, on by default) [#wisdom-de-attributed-on-by-default] When the `wisdom` capability is on — which it is for every new agent — the platform runs a daily promotion job that pulls patterns from per-user fact histories, k-anonymizes them, and rewrites the result through an LLM into de-attributed knowledge. No individual user is identifiable. Every agent benefits from "what tends to work / what tends to come up" without ever leaking who said what. This is your free generalization layer. There's nothing for agents to call — wisdom shows up alongside facts in the agent's context automatically when the capability is on. ```ts // Wisdom is on by default for every new agent. To opt out for a specific // agent (e.g. a single-user companion product where cross-user generalization // isn't appropriate), pass false at create time or via updateCapabilities: await client.agents.updateCapabilities("agent_abc", { wisdom: false }); ``` Wisdom is enabled for **all agents** — including ones created before the default-on cutover. The capability stores tri-state: `true`, `false`, or unset (treated as on). Pass `wisdom: false` explicitly only when you want to disable it; passing nothing keeps the agent on the platform default. ### Shared memory (attributed, opt-in) [#shared-memory-attributed-opt-in] Some businesses want the opposite of de-attribution — they want users working with the same agent to see **who** is doing what. A team-collaboration agent might surface "Alice owns the migration, Bob is on incident response." A party-coordinator agent might track "Carol brings dessert, Dave does setup." That's what the `sharedMemory` capability gates. When this capability is on, the agent records person/entity-attributed facts (roles, expertise, business context, relationship edges) and exposes them to other users sharing the agent. Three things change: 1. **Tools.** The agent gets `wisdom_create`, `wisdom_update`, `wisdom_delete`, and relation edges, plus admin-side CSV import. 2. **Context.** Other users' attributed facts surface in the agent's per-turn context with attribution. 3. **Privacy floor.** Every write is validated against a privacy blocklist (compensation, health, politics) using a dedicated semantic validator before persistence — so the agent can't share something that shouldn't cross the user boundary even if a user asks it to. Shared memory is OFF by default. Enable it explicitly when the agent serves a group, team, or party that benefits from cross-user visibility. You can also enable it at agent creation time: `wisdom` is the generalization layer (safe, de-attributed, on by default). `sharedMemory` is the attribution layer (sensitive, per-person, off by default). Both can coexist — but turn on shared memory only when the use case genuinely needs cross-user visibility (groups, teams, parties, shared business context). Single-user companion products should leave it off. ## Tutorials [#tutorials] * [Inventory tutorial](./tutorial-inventory) — model per-user items as typed KB entities and read them back during conversations * [Medication Reminders](./tutorial-medication-reminders) — end-to-end flow combining Knowledge Base, Inventory, and Scheduled Reminders for a medication adherence product ## Next steps [#next-steps] * [Inventory](./inventory) — per-user structured items backed by the same knowledge graph * [Knowledge Analytics](./knowledge-analytics) — recommendation rules, trend rankings, and conversion tracking built on your entity graph * [Organization Knowledge Base](./organization-knowledge-base) — project-level shared knowledge visible to all agents across a tenant --- # Memory Source: https://sonz.ai/docs/en/memory > Agents remember facts, events, and commitments extracted automatically from every conversation, recalled on demand via search, timeline, and tree browsing. Memory is the persistence layer behind every agent relationship. Each conversation is analyzed to extract facts, events, and commitments — stored in a structured tree and recalled automatically before the next response. Memory also composes directly with [Scheduled Reminders](./scheduled-reminders): when a reminder fires and the user replies, the reply is captured as a new memory fact. It feeds [Agent Insights](./agent-insights) too — habits, goals, and interests are derived signals aggregated over memory facts. ## What you can build with it [#what-you-can-build-with-it] * **Relationship-arc companions** — agents that reference shared history ("the week you were stressed about finals") to deepen connection over months * **Context-aware work assistants** — skip re-asking for role, preferences, and recent tickets by seeding from CRM data on first run * **Compliance-ready enterprise agents** — every recalled fact carries source message IDs, making the agent's reasoning auditable at review time * **Adherence dashboards** — query memory after reminder fires to build medication or habit compliance views without a separate database * **Shared-memories UX** — render the timeline endpoint as a browsable "our story" view inside companion or wellness apps ## Quickstart [#quickstart] Search a user's memory by semantic query, then list the top-level tree for context. ## Core concepts [#core-concepts] ### Memory tree structure [#memory-tree-structure] Memory is organized as a hierarchical tree of **nodes**, each with a `NodeID`, `Title`, `Summary`, and optional child nodes. Nodes act as thematic containers — "Jane's work life," "travel experiences" — and hold atomic facts beneath them. You can navigate the tree by passing `parentID` to `list`, or fetch a subtree with `includeContents: true` to pull a node's facts in one call. Key `MemoryNode` fields: `NodeID`, `AgentID`, `UserID`, `ParentID`, `Title`, `Summary`, `Importance`, `CreatedAt`, `UpdatedAt`. ### Facts vs summaries [#facts-vs-summaries] **Facts** are atomic, source-anchored statements ("User is a senior product manager at Acme Corp"). Every fact traces back to a specific message in a real conversation — the agent cannot hallucinate memories. **Summaries** are auto-generated consolidations written at session end, giving long conversations a compact digest. Both live in the tree and both appear in search results. ### Timeline queries [#timeline-queries] `timeline` returns a chronological view organized by session — each `TimelineSession` carries `session_id`, `facts`, `first_fact_at`, `last_fact_at`, and `fact_count`. Use it to render episodic history in your UI or to audit what was extracted from a specific time window. ### Reset and scoping [#reset-and-scoping] `reset` deletes all memory for an agent–user pair and is irreversible. Use it for testing, privacy-right-to-erasure flows, or account handoffs. All write operations (`seed`, `createFact`, `search`) accept an `instanceId` to scope memory to a workspace or tenant, preventing cross-boundary leakage in multi-tenant deployments. ### Sync vs async memory recall [#sync-vs-async-memory-recall] Supplementary memory recall — the extra fact lookups that enrich each turn beyond the agent's automatic working set — runs **synchronously** by default: every fact lands in the current turn before generation starts. Switch to **`async`** when first-token latency matters more than completeness; recall races a deadline, and slow hits spill into the next turn. `memory_mode` is an agent-wide capability. Set it once via `update_capabilities()`; every subsequent chat uses that mode until you change it. There is no equivalent at agent-creation time — create the agent first, then flip the mode. > **When to pick async**: high-volume voice agents, mobile clients on slow networks, or any setup where missing one or two enrichment facts is preferable to a 200ms latency spike. The agent's automatic working set still lands on every turn — only supplementary recall slips. ### Pending capabilities [#pending-capabilities] `AgentCapabilities.pendingCapabilities` is a list of capability changes that have been queued by the platform but not yet applied — for example, a tier upgrade that will unlock music or video generation. Each entry carries a `capability` name (string) and an optional `context` string with human-readable detail. Read it via `get_capabilities()` to surface upgrade status in your UI. ## Full API [#full-api] All methods are on `client.agents.memory.*` (TS/Python) or `client.Agents.Memory` (Go). Full request/response shapes live in the [API reference](./api-reference). | Method | Returns | Description | | ----------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `list(agentID, opts)` | `MemoryTreeResponse` | Browse the memory tree, optionally rooted at a `parentID`. Pass `memory_type` to filter results to a specific memory category: `"factual"`, `"episodic"`, `"semantic"`, `"procedural"`, `"identity"`, `"temporal"`, or `"relational"`. This is a post-fetch filter applied on the result set — it does not reduce server-side I/O, so the `limit` applies before filtering. | | `search(agentID, opts)` | `MemorySearchResponse` | Semantic/keyword search; returns `Results[]` with `content`, `factType`, `score`. Pass `userId` (`user_id` in Python/JSON) to scope results to a single user; omit to search across all users for the agent. | | `timeline(agentID, opts)` | `MemoryTimelineResponse` | Chronological sessions with `first_fact_at`, `last_fact_at`, `fact_count` | | `listFacts(agentID, opts)` | `FactListResponse` | Paginated flat list of atomic facts; response has `Facts`, `TotalCount`, `HasMore` | | `reset(agentID, opts)` | `MemoryResetResponse` | Delete all memory for an agent–user pair | | `createFact(agentID, opts)` | `AtomicFact` | Manually insert a fact tagged `source_type="manual"` | | `updateFact(agentID, factID, opts)` | `AtomicFact` | Patch content, type, importance, or confidence of an existing fact | | `deleteFact(agentID, factID)` | `void` | Remove a single fact by ID | | `seed(agentID, opts)` | `SeedMemoriesResponse` | Bulk-import initial memories without an AI generation step | | `deleteWisdomFact(agentID, factID)` | `DeleteWisdomResponse` | Remove a wisdom-layer fact | | `getWisdomAudit(agentID, factID)` | `WisdomAuditResponse` | Full audit trail for a wisdom fact | | `getFactHistory(agentID, factID)` | `FactHistoryResponse` | Version history for a specific fact | ## Combines with other features [#combines-with-other-features] ### With [Scheduled Reminders](./scheduled-reminders) — responses populate memory [#with-scheduled-reminders--responses-populate-memory] When a scheduled reminder fires and the user replies, the memory layer auto-captures the reply as a fact. Query those facts later to build a compliance view or adherence dashboard without an extra database. ```typescript // After a week of daily medication reminders, query the captured replies const memories = await client.agents.memory.search("agent-id", { query: "medication taken ibuprofen", limit: 10, }); for (const result of memories.results) { console.log(result.content, result.score); // "User confirmed taking 500mg ibuprofen at 08:14" 0.89 } ``` The full reminder-to-memory flow is shown in the [Medication Reminders tutorial](./tutorial-medication-reminders). ### With [Conversations](./conversations) — every turn writes memory [#with-conversations--every-turn-writes-memory] Memory is fully automatic during chat — you do not call any write endpoint yourself. The platform analyzes each conversation turn, extracts facts, events, and commitments, and stores them in the tree. The next time you call chat for that agent–user pair, the most relevant memories are assembled into context automatically. ```typescript // Just call chat — memory extraction and retrieval happen on every turn const stream = client.agents.chat.stream("agent-id", { userId: "user-123", messages: [{ role: "user", content: "I've been training for a half marathon." }], }); // After the conversation, the fact "user is training for a half marathon" // is stored automatically — no extra call needed. const results = await client.agents.memory.search("agent-id", { query: "running training marathon", limit: 5, }); console.log(results.results[0].content); // "User is training for a half marathon" ``` ### With [Agent Insights](./agent-insights) — memory is the raw material [#with-agent-insights--memory-is-the-raw-material] Habits, goals, interests, and mood trends are derived signals the context engine aggregates over memory facts. Memory is what the engine reads; Agent Insights is what the engine produces. Search memory for raw facts, then call Agent Insights to see what those facts have been distilled into. ```typescript // 1. Fetch raw memory facts about fitness const facts = await client.agents.memory.search("agent-id", { query: "exercise fitness running", limit: 10, }); // 2. Fetch the derived habit signal the engine built from those facts const habits = await client.agents.listHabits("agent-id", { userId: "user-123", }); console.log(habits.habits); // [{ label: "Daily runner", frequency: "daily", confidence: 0.91 }] ``` ## Tutorials [#tutorials] * [Memory — end-to-end walkthrough](./tutorial-memory) — covers seed, search, timeline, manual facts, and reset. ## Next steps [#next-steps] * [Agent Insights](./agent-insights) — derived signals (habits, goals, interests) built on top of memory facts. * [Scheduled Reminders](./scheduled-reminders) — proactive messages whose user replies flow back into memory. * [Conversations](./conversations) — every chat turn is the primary source of memory writes. --- # Multiplayer Memory Source: https://sonz.ai/docs/en/multiplayer-memory > One umbrella for every way memory crosses boundaries in Sonzai — agent-to-agent (inter-agent) through the shared knowledge base, and across users sharing one agent (intra-agent) through wisdom and shared memory. A closed-loop company brain plus a team brain, combined. The default agent memory model is **per-pair** — every conversation builds a fact profile scoped to one `(agent, user)` pair. That isolation is the right default for privacy, but the moment your product has more than one agent or more than one user per agent, you want memory to cross the boundary in controlled, observable ways. Multiplayer memory is the umbrella for those crossing capabilities. It splits cleanly along two axes: | Axis | What crosses | Real-world shape | Capabilities | | --------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | **Inter-agent** | Knowledge between agents on the same project (or tenant). | A closed-loop **company brain** — agent A learns; agent B picks it up. | `knowledgeBase` (read), `knowledgeBaseWrite` (autonomous update), `knowledgeBaseScopeMode` (org-wide cascade). | | **Intra-agent** | Memory between users talking to the same agent. | A **team brain** — one agent informing user A with what it learned with user B. | `wisdom` (de-attributed, default-on), `sharedMemory` (attributed, opt-in). | Both axes can run simultaneously. The full picture: agents on the same project share the world they've learned about (inter-agent) *and* a single agent shares context about the people it serves (intra-agent). Same compounding curve, two dimensions. {` INTER-AGENT (across agents) shared knowledge base, autonomous updates, org-wide scope, closed-loop company brain | v +-------------------------+ +-------------------------+ | Agent A | | Agent B | | reads + writes KB |<---->| reads + writes KB | +-------------------------+ +-------------------------+ ^ ^ ^ ^ ^ ^ | | | INTRA-AGENT (across users) | | | | | | wisdom (de-attributed), | | | | | | shared memory (attributed) | | | +--------+ | +---------+ +-------+ | +---------+ | | | | | | user X1 user X2 user X3 user Y1 user Y2 user Y3 Inter-agent: anything any agent learns is grounded data for every other agent on the project. Intra-agent: a single agent carries memory across the users it serves -- with privacy guardrails. `} ## Inter-agent memory — agent ↔ agent [#inter-agent-memory--agent--agent] Inter-agent memory turns the project knowledge base into a closed-loop **company brain**: anything one agent learns or verifies during a conversation becomes grounded data every other agent on the project retrieves on the next session. Three layers stack from baseline to organization-wide. ### 1. Baseline read — every agent grounds replies in the project KB [#1-baseline-read--every-agent-grounds-replies-in-the-project-kb] Any agent with `knowledgeBase: true` reads the project knowledge graph during conversations via the `knowledge_search` tool. The graph is hand-curated, ETL-loaded, or both — see [How knowledge gets into the KB](./knowledge-base#how-knowledge-gets-into-the-kb) for the two ingestion paths. ```ts await client.agents.updateCapabilities("agent_abc", { knowledgeBase: true, }); ``` ### 2. Autonomous editing — agents write what they learn back [#2-autonomous-editing--agents-write-what-they-learn-back] Flip `knowledgeBaseWrite: true` and the agent gets `knowledge_create / knowledge_update / knowledge_delete` tools. During conversations the agent records verified facts itself, with a full audit trail (`source = "agent:"`) and compare-and-swap update semantics so admin edits don't get clobbered. The next agent that runs `knowledge_search` on the same topic retrieves what the previous agent wrote down. ```ts await client.agents.updateCapabilities("agent_abc", { knowledgeBase: true, knowledgeBaseWrite: true, }); ``` Use this when **the source of truth IS the conversation** — support agents recording verified incident details, customer-success agents capturing renewal context, scribe agents writing meeting notes. Detail: [Agents writing to the knowledge base](./knowledge-base#agents-writing-to-the-knowledge-base). ### 3. Organization scope — tenant-wide knowledge above projects [#3-organization-scope--tenant-wide-knowledge-above-projects] Set `knowledgeBaseScopeMode: "cascade"` on an agent and it reads from both the project KB and the org-scope KB on every search. The org scope is for tenant-wide artefacts: policies, lore, brand, reference catalogs. Project wins on collisions; org fills in defaults. ```ts await client.agents.updateCapabilities("agent_abc", { knowledgeBase: true, knowledgeBaseScopeMode: "cascade", }); ``` Detail: [Organization Knowledge Base](./organization-knowledge-base). ## Intra-agent memory — across users sharing one agent [#intra-agent-memory--across-users-sharing-one-agent] Intra-agent memory turns a single agent into a **team brain**: one agent serving multiple users carries memory that crosses the user boundary, so it can inform user A with the context it gathered while talking to user B. Two complementary tiers. ### 1. Wisdom — default-on, de-attributed [#1-wisdom--default-on-de-attributed] `wisdom` is on for every new agent. A daily promotion job pulls patterns from per-user fact histories, **k-anonymises** them, and rewrites the result through an LLM into agent-wide knowledge. No individual user is identifiable. Every agent benefits from "what tends to work / what tends to come up" without ever leaking who said what. ```ts // Wisdom is on by default. Pass false only to opt out // (rare — usually only for strict single-user products). await client.agents.updateCapabilities("agent_abc", { wisdom: false }); ``` This is the **safe** intra-agent layer — privacy-protected by construction, no opt-in required. ### 2. Shared memory — opt-in, attributed [#2-shared-memory--opt-in-attributed] `sharedMemory: true` is the **powerful** intra-agent layer. The agent records person/entity-attributed facts (roles, expertise, business context, relationships) and surfaces them to other users sharing the agent — with names visible. "Alice owns the migration; Bob is on incident response." "Carol brings dessert; Dave does setup." ```ts await client.agents.updateCapabilities("agent_abc", { wisdom: true, // precondition; default on sharedMemory: true, }); ``` Three things flip when you turn it on: the agent gets `sonzai_wisdom_set/update/delete/relate` tools; the prompt grows a "Shared facts" section with a discretion clause; every write is server-side validated against a privacy floor (compensation, health, politics blocked). Every disclosure is logged to the audit table. Full detail: [Shared Memory](./shared-memory). ## Combining inter-agent and intra-agent [#combining-inter-agent-and-intra-agent] The two axes are independent — every combination is valid: | Inter-agent | Intra-agent | What you get | | ----------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- | | Off | Off | Per-pair memory only. Right default for single-user companion products. | | On (read only) | Off | Agents ground replies in your KB but don't share between users. Standard read-only docs assistant. | | On (read + write) | Off | Closed-loop world knowledge. Agents capture verified facts about products, prices, incidents — every other agent benefits. | | Off | On | Team brain — one agent serves a group, but no shared world knowledge across agents. | | On (read + write) | On | **Full multiplayer memory.** Closed-loop world knowledge plus a team brain. Best for shared-business-context products. | ```ts // Full multiplayer memory in one capability update await client.agents.updateCapabilities("agent_abc", { knowledgeBase: true, knowledgeBaseWrite: true, // inter-agent: closed-loop KB knowledgeBaseScopeMode: "cascade", // inter-agent: org scope wisdom: true, // intra-agent: de-attributed (default on) sharedMemory: true, // intra-agent: attributed }); ``` ## How to verify it's working [#how-to-verify-its-working] Each capability has a live read endpoint you can hit to confirm the loop closes. Replace `$AGENT_ID`, `$PROJECT_ID`, `$API_KEY` with your own. **Inter-agent — KB writes** ```bash # Search the project KB — does an agent write show up? curl 'https://api.sonz.ai/api/v1/projects/$PROJECT_ID/knowledge/search?q=YourQuery' \ -H "Authorization: Bearer $API_KEY" ``` **Intra-agent — attributed shared memory** ```bash # List attributed facts on the agent curl 'https://api.sonz.ai/api/v1/agents/$AGENT_ID/wisdom/attributed?limit=20' \ -H "Authorization: Bearer $API_KEY" # Read the disclosure audit — every fact disclosed in a turn is logged curl 'https://api.sonz.ai/api/v1/agents/$AGENT_ID/wisdom/audit?limit=50' \ -H "Authorization: Bearer $API_KEY" ``` **Intra-agent — wisdom (default-on)** The de-attributed wisdom layer surfaces inline in every prompt the agent runs once the daily promotion job has scanned per-user fact histories — no separate read endpoint. To verify it's running, watch agent context size over a 48-hour window after multi-user traffic; you should see the wisdom block populate. ## Privacy and control [#privacy-and-control] Multiplayer memory is sensitive by design. Each capability has its own controls — none of them require trusting the LLM: | Capability | Server-side controls | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `knowledgeBaseWrite` | Schema validation per write, write quotas, project-scope check, full audit trail (`source = "agent:"`), CAS update, soft-delete only (hard delete admin-only). | | `wisdom` | k-anonymity threshold before promotion, LLM-gated rewrite to remove identifying detail, daily cadence so a single noisy session can't leak. | | `sharedMemory` | Semantic privacy-floor validator (compensation, health, politics blocked), discretion clause in every prompt, disclosure audit on every fact load, soft-delete tombstone, source pinned to `developer_api` (callers can't spoof provenance). | Every cross-boundary flow has a corresponding read endpoint, so you can audit retrospectively at any time. ## Where to dive deeper [#where-to-dive-deeper] * [Knowledge Base](./knowledge-base) — the inter-agent surface: ingestion paths, schemas, autonomous editing detail * [Organization Knowledge Base](./organization-knowledge-base) — the org-wide cascade detail * [Shared Memory](./shared-memory) — the intra-agent surface: enable/disable, four wisdom tools, privacy floor, full verification probes * [Self-Improvement](./self-improvement) — how multiplayer memory layers on top of per-pair online learning * [Wisdom API](./reference/api/wisdom) — full endpoint reference for shared memory CRUD + audit --- # Notifications (Polling) Source: https://sonz.ai/docs/en/notifications-polling > Fetch pending proactive messages for a user by polling the Notifications API — the simplest way to consume schedules, wakeups, and tenant-triggered events in a web or mobile client. Proactive messages — generated by recurring schedules, one-off wakeups, or tenant-triggered events — land in a per-user notifications queue the moment they fire. Your frontend or backend polls that queue to fetch pending messages, display them to the user, and mark each one consumed. No push infrastructure, no webhook endpoint, no server-side listener to maintain — just an HTTP GET on your schedule. This is the recommended delivery pattern for web clients and mobile apps that can't accept inbound HTTP requests, and it doubles as a handy catch-up mechanism for users who were offline when messages were generated. ## What you can build with it [#what-you-can-build-with-it] * **Mobile app inbox** — periodic fetch of pending messages on foreground, display as native notifications or in-app banners * **Web dashboard** — a dedicated tab showing all of the agent's pending outreach to a given user * **Delayed delivery** — let messages queue up while the user is offline; deliver the full batch on reconnect * **Audit / moderation** — preview agent-generated messages before pushing to downstream delivery (email, SMS) * **Development / testing** — poll notifications during a test run to verify that schedules and wakeups fired correctly ## Quickstart [#quickstart] Poll for pending messages, display the latest, then mark each one consumed. ## Core concepts [#core-concepts] ### Queue semantics [#queue-semantics] When a proactive message fires — whether from a schedule, a wakeup, or a trigger event — the platform enqueues it for the relevant user. The queue is per-user, per-agent. Calling `list` returns only messages in `pending` state; calling `consume` transitions a specific message to `consumed`. Consumed messages are excluded from future `list` responses but remain visible in `history`. The queue does not auto-expire: messages stay pending indefinitely until your code marks them consumed. ### Polling cadence [#polling-cadence] There is no hard requirement on how frequently you poll, but these guidelines work well in practice: * **Foreground (user has the app open):** every 10–30 seconds * **Background (app backgrounded or tab hidden):** every 2–5 minutes, or on visibility-change events * **Reconnect burst:** poll immediately when the user comes back online after a period of inactivity, then resume normal cadence Avoid polling more frequently than every 10 seconds — there is no benefit since notification generation is event-driven, not continuous. ### Vs SSE (live chat stream) [#vs-sse-live-chat-stream] If the user has an active SSE chat stream open, proactive messages appear inline in the conversation automatically — no polling needed. Polling is the **catch-up** mechanism for users who do not have a live stream. The two patterns are complementary: SSE for foreground delivery, polling for background or offline users. ### History endpoint [#history-endpoint] `notifications.history` is separate from `notifications.list`. It returns all historical notifications for an agent (including already-consumed ones) and is useful for audit trails, moderation dashboards, and debugging. It does not filter by `user_id` — it returns across all users up to the requested limit. ## Full API [#full-api] All methods are on `client.agents.notifications.*` (TS/Python) or `client.Agents.Notifications` (Go). Full request and response shapes live in the [API reference](./api-reference). | Method | Signature | Returns | Description | | --------- | ------------------------------------- | ----------------------------------- | ------------------------------------------------------- | | `list` | `list(agentId, { user_id?, limit? })` | `{ notifications: Notification[] }` | Fetch pending messages for a user | | `consume` | `consume(agentId, messageId)` | `void` | Mark a single message consumed | | `history` | `history(agentId, limit)` | `{ notifications: Notification[] }` | Fetch all historical notifications (consumed + pending) | ### Notification fields [#notification-fields] | Field | JSON key | Description | | ------------------ | ------------------- | -------------------------------------------------------------------- | | `MessageID` | `message_id` | Pass this to `consume` to mark the message delivered | | `UserID` | `user_id` | The user this notification was generated for | | `CheckType` | `check_type` | The check type (e.g. `"reminder"`, `"interest_check"`, `"birthday"`) | | `GeneratedMessage` | `generated_message` | The actual text the agent produced — display this to the user | | `CreatedAt` | `created_at` | When the message was enqueued (RFC 3339 UTC) | | `ScheduleID` | `schedule_id` | Set if the message originated from a schedule; otherwise absent | | `WakeupID` | `wakeup_id` | Set if the message originated from a wakeup; otherwise absent | Older code may use `id`, `notificationId`, `type`, or `content`. These are incorrect. The canonical fields are `message_id`, `check_type`, and `generated_message`. Using the wrong field names will result in silent failures when calling `consume`. ## Combines with [#combines-with] ### With [Scheduled Reminders](./scheduled-reminders) — delivery side of recurring messages [#with-scheduled-reminders--delivery-side-of-recurring-messages] A schedule defines *when* the agent fires; polling is one way to *receive* what it produced. When a schedule's cadence fires, the platform generates the agent's message and enqueues it. Your client polls, displays `generated_message`, then calls `consume` to clear it from the queue. The schedule and delivery are fully decoupled — you can swap in webhooks or SSE without touching the schedule definition. ### With [Wakeups](./wakeups) — delivery side of one-off check-ins [#with-wakeups--delivery-side-of-one-off-check-ins] A wakeup fires once at a specific moment; polling retrieves the message it generated. This is the natural delivery pattern for one-off agent outreach in mobile clients where webhooks are unavailable. Schedule the wakeup when the event is known (e.g. "follow up 24 hours after purchase"), then poll periodically — the message lands in the queue the moment the delay elapses. ### With [Webhooks](./webhooks) — alternative push delivery [#with-webhooks--alternative-push-delivery] Polling and webhooks are two delivery patterns for the same underlying notifications queue. Choose based on your infrastructure: * **Polling** — your client asks the server for new messages on a schedule. Simple to implement, works in browsers and mobile apps, no inbound connectivity required. Latency is bounded by your polling interval. * **Webhooks** — the server pushes each message to a URL you register the moment it fires. Lower latency, better for server-to-server integration and multi-channel fanout (email, SMS, push notifications). Requires a public HTTPS endpoint to receive callbacks. You can use both simultaneously: poll from mobile clients for in-app delivery and register a webhook on your backend for email/SMS fanout. The queue tracks consumed state per message, so a message consumed via polling will not appear in webhook delivery (and vice versa). ## Tutorials [#tutorials] * [Medication Reminders](./tutorial-medication-reminders) — full-stack example combining Schedule + Inventory + Memory; shows the end-to-end flow from schedule creation to polling the generated reminder. * [Scheduled Reminders — reference walkthrough](./tutorial-scheduled-reminders) — covers cadence shapes, DST, preview, and the full lifecycle including how fired messages appear in the notifications queue. ## Next steps [#next-steps] * [Scheduled Reminders](./scheduled-reminders) — set up the recurring schedules whose output you poll here * [Wakeups](./wakeups) — one-off check-ins that also land in the notifications queue * [Webhooks](./webhooks) — the push alternative to polling; compare delivery trade-offs * [Proactive Messaging overview](./proactive-overview) — sources × delivery channels explained --- # Organization-Global Knowledge Base Source: https://sonz.ai/docs/en/organization-knowledge-base > One organization-wide knowledge scope that every project under the tenant can read from. Use it for policies, shared lore, playbooks, or facts that belong to the whole organization rather than a single project. **The organization-global Knowledge Base** is an opt-in second scope that sits above every project's own [Knowledge Base](./knowledge-base), letting agents across all projects under a tenant read shared facts — HR policies, brand standards, product catalogs, multi-game lore — without duplicating data per project. Each agent picks a scope mode (`project_only`, `org_only`, `cascade`, or `union`) to control how org and project graphs combine. Cascade is the recommended default: project facts win on ID collisions, so local overrides remain authoritative. ## When to use it [#when-to-use-it] By default, the Knowledge Base is **project-scoped**. Every project has its own isolated graph. That is the right model for most tenants — a project's data should not leak into other projects' agents. The **organization scope** is an opt-in second scope that sits above every project. Knowledge written here is readable by every project agent under the tenant that opts into a cross-scope reading mode. Typical uses: * Company-wide policies (HR, refund, privacy, terms). * Product documentation shared across projects. * Brand guidelines, tone standards, style rules. * Shared lore for multi-game or multi-product characters. * Reference catalogs (locations, entities, product lists). ## How it fits with project KB [#how-it-fits-with-project-kb] {` Tenant (organization) | |-- Organization-global KB (scope_id = "") | - policies, shared lore, brand, reference catalogs | - written by tenant admins via the org endpoints | |-- Project A KB (scope_id = project_a_id) | - A's own uploaded docs + API-pushed facts | |-- Project B KB (scope_id = project_b_id) | - B's own uploaded docs + API-pushed facts | Agents under any project choose how to read across the two scopes: - project_only legacy: just the agent's project KB - org_only only the organization-global KB - cascade both, project wins on ID collisions (recommended) - union both, first occurrence wins `} ## Scope mode on an agent [#scope-mode-on-an-agent] Every agent has a `knowledgeBaseScopeMode` capability. Leaving it unset preserves the legacy project-only behavior. To enable the cascade, set it via the capabilities endpoint or the dashboard. Enable the knowledge base capability and set the project ID via the SDK: ## Writing to the org scope [#writing-to-the-org-scope] There are two ways a tenant admin can populate the org scope. Both are backend-only endpoints — end users of your products never see them. ### 1. Create a node directly in the org scope [#1-create-a-node-directly-in-the-org-scope] Use this for hand-authored facts that should live at the organization level from the start. ### 2. Promote an existing project node [#2-promote-an-existing-project-node] If a fact already lives in a project KB and you want to share it organisation-wide, promote it. The project copy is preserved — promotion is additive. If an org node with the same `(node_type, norm_label)` already exists, the server returns that one instead of writing a duplicate. ## Reading: cascade search and provenance [#reading-cascade-search-and-provenance] When an agent with a non-default scope mode calls `knowledge_search` during a conversation, the platform runs the search against both scopes in parallel and fuses the results using Reciprocal Rank Fusion (RRF). Each returned result carries a `scope` field so your prompt can show the LLM where a fact came from. {` Agent's knowledge_search("refund policy") | v +----------------------------+ +----------------------------+ | Project BM25 (scope=proj) | +--> | Org BM25 (scope="") | +----------------------------+ +----------------------------+ | | +--------------- RRF fuse ----------------+ | v Top-N results, each tagged: scope: "project" | "organization" `} Scope modes differ in how they merge on a collision: * **`cascade`** (recommended): project wins on duplicate node IDs. Agents keep their own overrides, but inherit the org defaults when a project doesn't define something. * **`union`**: first occurrence wins; both scopes contribute equally to ranking. Useful when you want broad coverage without a strong preference. * **`org_only`**: skip project KB entirely. Useful for reference-only agents (FAQ bots on company policy, e.g.). * **`project_only`** (default): legacy behavior, org-scope facts are invisible to this agent. ## Dashboard admin UI [#dashboard-admin-ui] A tenant admin can manage org-scope knowledge at `/dashboard/knowledge/org`: * Create a node directly in the org scope. * Promote an existing project node by pasting its project ID and node ID. The UI is a thin wrapper over the same endpoints shown above; if you need bulk operations or automated pipelines, the SDKs are the recommended path. ## Operational notes [#operational-notes] * **Access control:** the two org-scope write endpoints are gated by the same tenant-admin middleware used by the existing project-scoped KB endpoints. Standard project members see no new surface. * **Backward compatibility:** zero change for any existing agent. Agents stay on `project_only` mode unless you set a scope mode explicitly. * **Idempotency:** dedup is at `(node_type, norm_label)`. Promotion returns the existing org node if one is already there; direct `createOrgNode` will create a second node with a different NodeID — check before calling if that matters. * **Per-scope BM25:** each scope maintains its own BM25 index and document-frequency corpus. This is why the cascade uses RRF instead of score-adding — the raw scores from two separate indexes are not directly comparable. --- # Personality System Source: https://sonz.ai/docs/en/personality > Create agents with distinct personalities and watch them evolve through interaction. **Personality** in Sonzai is a Big Five (OCEAN) profile attached to every agent, mapped internally to ten BFAS facets and a set of behavioral traits — response length, question frequency, empathy style, conflict approach — that shape how the agent actually talks. You set five 0.0-1.0 scores at creation; the Relationship Layer derives the prompt, speech patterns, and mood baselines from there. The most load-bearing detail: personality drifts slowly through interaction, with safety caps to prevent runaway change, and you can inspect the full evolution history at any time. ## Big Five Personality Model [#big-five-personality-model] Every agent has Big Five (OCEAN) personality scores. Behavioral traits, mood baselines, speech patterns, and interaction preferences all derive from these scores. * **Openness** (0.0 - 1.0): Curiosity, creativity, openness to experience. High = imaginative, adventurous. Low = practical, conventional. * **Conscientiousness** (0.0 - 1.0): Organization, discipline, goal-orientation. High = methodical, reliable. Low = spontaneous, flexible. * **Extraversion** (0.0 - 1.0): Social energy, enthusiasm, assertiveness. High = outgoing, energetic. Low = reserved, reflective. * **Agreeableness** (0.0 - 1.0): Warmth, cooperation, empathy. High = caring, trusting. Low = direct, skeptical. * **Neuroticism** (0.0 - 1.0): Emotional sensitivity, anxiety tendency. High = emotionally reactive. Low = emotionally stable. The `confidence` field (0.0-1.0) controls how strongly scores influence behavior. Low confidence = more generic; high = more differentiated. ### BFAS Facet Dimensions [#bfas-facet-dimensions] Internally, the platform maps Big5 scores to 10 BFAS (Big Five Aspect Scales) facets. These facets provide finer-grained control over personality and are exposed in the personality profile response: | Big5 Domain | Facet 1 | Facet 2 | | ----------------- | ----------------- | --------------- | | Openness | `intellect` | `aesthetic` | | Conscientiousness | `industriousness` | `orderliness` | | Extraversion | `enthusiasm` | `assertiveness` | | Agreeableness | `compassion` | `politeness` | | Neuroticism | `withdrawal` | `volatility` | Each facet is a 0.0-1.0 score derived from the parent Big5 dimension. You can read them from the personality profile but do not need to set them manually — they are computed from your Big5 scores. ### Behavioral Traits [#behavioral-traits] The personality profile includes derived behavioral traits that shape how the agent communicates: * `response_length` — How verbose or concise the agent tends to be. * `question_frequency` — How often the agent asks follow-up questions. * `empathy_style` — The agent's approach to emotional support (validating, solution-oriented, etc.). * `conflict_approach` — How the agent handles disagreements (accommodating, direct, mediating, etc.). ## Create an Agent with Personality [#create-an-agent-with-personality] Pass Big Five scores when creating an agent. The platform automatically generates a personality prompt, speech patterns, and behavioral tendencies. Passing the same `agentId` always upserts. Safe to call on every deploy. See [Quickstart](/docs/en/guides/getting-started) for the recommended UUID derivation pattern. ## Get Personality Profile [#get-personality-profile] Retrieve the current personality profile for an agent, including derived speech patterns and interaction preferences. ## Update Personality [#update-personality] Update Big5 scores after running a personality assessment. The `confidence` value controls how strongly the new scores influence behavior. * **confidence \< 0.3**: Tentative. Minimal adjustments. * **confidence 0.3 - 0.7**: Blended with existing scores. * **confidence > 0.7**: Strongly influences personality. ## Personality Evolution History [#personality-evolution-history] Retrieve the history of personality shifts for an agent — useful for surfacing growth moments to users. ## Interaction Preferences [#interaction-preferences] Derived preferences that shape the conversation style: ## Personality Evolution [#personality-evolution] Personalities evolve naturally through interactions: 1. **Interaction Analysis** — Emotional themes and patterns are analyzed after each conversation. 2. **Micro-Shifts** — Small adjustments are applied to relevant Big5 dimensions based on conversation content. 3. **Breakthroughs** — When cumulative shifts cross a threshold, a 'breakthrough' event fires — a significant personality change the agent becomes aware of. 4. **Profile Regeneration** — Personality prompt, speech patterns, and behavioral instructions are regenerated to reflect the evolved personality. ## Per-User Personality Overlays [#per-user-personality-overlays] The platform automatically derives a per-user personality overlay — how the agent subtly adapts to a specific user based on their conversation history, preferences, and relationship state. You don't set overlays manually; they're populated by the same pipeline that runs after every chat turn. Read the current overlay for UI (show how the agent's tone shifts per user) or analytics: ## Fork an Agent [#fork-an-agent] Create an independent copy of an agent with its own personality, memory, and state. The forked agent starts with the same configuration as the original but evolves independently from that point forward. ## In Practice [#in-practice] All three audiences use personality, but what you tune and why differs sharply. **Personality is brand voice and compliance guard-rails.** Enterprise agents inherit your company's tone of voice and must never sound "off brand" in front of customers. Low openness + high conscientiousness + low neuroticism gives the most predictable output. **Pin the profile.** Expose the Big5 config via your config service so brand/compliance teams can adjust without developer intervention. Regenerate on pin changes; store the version for audit. **Monitor with personality history.** A sudden trait shift is a signal that something odd happened in a conversation (bad instruction, jailbreak attempt). Alert ops when any trait drifts more than ±0.05 in a 24h window. ```typescript const shifts = await client.agents.personality.history("agent-id"); for (const s of shifts.shifts) { if (Math.abs(s.delta) > 0.05) alertOps(s); } ``` ## Combines with other features [#combines-with-other-features] ### With [Self-Improvement](./self-improvement) — personality evolves over time [#with-self-improvement--personality-evolves-over-time] The post-processing pipeline runs after every session and can push Big5 updates back into the personality profile. Use `Personality.Get` before and after a session to observe evolution events and surface growth moments to users. The `triggeredBy` field ties each shift back to the session or event that caused it, giving you an audit trail for every personality change. ### With [Generation](./generation) — initial personality from character generation [#with-generation--initial-personality-from-character-generation] `GenerateCharacter` produces a fully-formed Big5 profile as part of its output. You can use that as the starting point for an agent and then refine scores with `Personality.Update` once you know how you want the character to feel in practice. ### With [User Personas](./user-personas) — agent personality × user persona = interaction style [#with-user-personas--agent-personality--user-persona--interaction-style] The agent's Big5 profile is one half of every conversation; the user's persona is the other. The platform combines both at context-build time: a high-agreeableness agent talking to an introverted user will naturally soften its tone and ask fewer questions, while the same agent talking to an assertive user will match energy and be more direct. You don't wire this up manually — pass the `userId` on each chat turn and the platform resolves the right overlay automatically: The per-user overlay is updated automatically by the pipeline — you read it; you don't write it. --- # Priming Source: https://sonz.ai/docs/en/priming > Bootstrap agents with user context — display names, demographic facts, chat transcripts from legacy systems, or bulk imports from CSV/CRM. Priming is how you tell a new agent what it already knows about a user. Instead of waiting for the agent to learn through conversation, you deliver the relevant facts up front: who the user is, where they came from, and what they've said before — all before the first message is exchanged. ## What you can build with it [#what-you-can-build-with-it] * **Migrations from other LLM frameworks** — import chat history from Zep, Mem0, Letta, OpenAI Assistants, LangChain, Character.AI, or any custom transcript store * **CRM / CSV bulk imports** — prime thousands of users in one call with structured contact data * **Chat-transcript seeding** — let the agent "remember" previous conversations from another system * **Display-name + timezone bootstrap** — ensure the agent addresses users correctly from turn 1 * **Onboarding enrichment** — load journal entries, support tickets, or prior interactions so the agent sounds familiar on the user's very first chat ## Quickstart [#quickstart] Prime a single user with their display name, timezone, and a short narrative block: The call returns immediately with a `job_id`. LLM fact-extraction runs asynchronously in the background — the primed facts appear in memory within seconds. ## Core concepts [#core-concepts] ### Metadata vs content [#metadata-vs-content] These are two distinct channels for different kinds of information: * **Metadata** is structured and first-class: `display_name`, `company`, `title`, `email`, `phone`, `timezone`, and a `custom` map for anything else. Sonzai generates facts from metadata fields synchronously — no LLM extraction required — so `facts_created` is non-zero even with no content blocks. * **Content** is narrative. Content blocks go through the full LLM extraction pipeline and end up as facts in the agent's memory constellation, exactly as if the user had said those things in a conversation. ### Content block types [#content-block-types] Each `PrimeContentBlock` has a `type` and a `body`: | Type | When to use | | ------------------- | ---------------------------------------------------------------------------------------------------- | | `"text"` | Narrative facts, bullet-point summaries, freeform notes about the user | | `"chat_transcript"` | A prior conversation from another system. Format as `User: …\nAgent: …` lines, one session per block | The extraction pipeline deduplicates across all blocks — you can safely send both raw transcripts and pre-extracted facts from the same source without producing duplicate memories. ### Batch vs single [#batch-vs-single] | Method | Use when | | ------------- | --------------------------------------------------------------------- | | `primeUser` | Onboarding a single user, enriching an existing user with new content | | `batchImport` | Migrating many users at once from a CSV, CRM, or legacy system | Batch imports return a job ID immediately. Use `getImportStatus` (or `GetImportStatus` in Go) to poll until the job's `status` reaches `"complete"`. ### Import jobs [#import-jobs] Both `primeUser` and `batchImport` are async. The `ImportJob` response carries: | Field | Meaning | | ----------------- | ---------------------------------------------------- | | `job_id` | Opaque ID — use it to poll status | | `status` | `"pending"`, `"processing"`, `"complete"`, `"error"` | | `total_users` | Number of users submitted (batch only) | | `processed_users` | Users fully extracted so far (batch only) | | `facts_created` | Total facts written to memory | | `error_message` | Set if the job errored | ## Batch import [#batch-import] Import multiple users in one call. Useful for migrating from a CRM or seeding a fresh deployment: ## Full API [#full-api] | Method | Returns | Description | | ---------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------- | | `primeUser(agentId, userId, opts)` | `PrimeUserResponse` | Prime or re-prime a single user. Async — returns a job ID. | | `getPrimeStatus(agentId, userId, jobId)` | `ImportJob` | Check status of a single-user priming job. | | `addContent(agentId, userId, opts)` | `AddContentResponse` | Append more content blocks to an already-primed user without overwriting metadata. | | `getMetadata(agentId, userId)` | `UserPrimingMetadata` | Fetch the stored structured metadata for a user. | | `updateMetadata(agentId, userId, opts)` | `UpdateMetadataResponse` | Partially update metadata fields. Provided keys overwrite; omitted keys are preserved. | | `batchImport(agentId, opts)` | `BatchImportResponse` | Import many users in one async job. | | `getImportStatus(agentId, jobId)` | `ImportJob` | Poll a batch import job by ID. | | `listImportJobs(agentId, limit?)` | `ImportJobListResponse` | List recent import jobs for an agent. | Calling `primeUser` more than once for the same user is safe. Content blocks are processed through the same deduplication pipeline as live chat — repeated or overlapping facts are merged, not doubled. ## Combines with other features [#combines-with-other-features] ### With [Memory](/docs/memory) — primed facts become durable memory [#with-memory--primed-facts-become-durable-memory] Content blocks flow through the exact same extraction pipeline as conversational messages. After priming, you can search for primed facts via `memory.search`: Primed facts carry a `source_type` matching the `source` string you passed to `primeUser` or `batchImport`, so you can distinguish migrated history from organically-learned facts when querying. ### With [Inventory](/docs/api/inventory) — structured data import via priming [#with-inventory--structured-data-import-via-priming] Use `structured_import` inside `primeUser` to seed per-user inventory items alongside narrative facts. This is how you import ownership tables, subscription rosters, or product holdings from a CRM export: ```json { "source": "crm_inventory", "structured_import": { "entity_type": "product", "content_csv": "product_name,quantity,purchase_date\nHiking Backpack,1,2025-09-01\nWater Bottle,2,2025-10-12", "column_mapping": { "product_name": { "property": "name", "is_label": true }, "quantity": { "property": "quantity", "type": "number" }, "purchase_date": { "property": "purchased_on" } } } } ``` Each row becomes a fact shaped as `"User owns