Skip to main content

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

  • Chat UIs (web, mobile) — stream tokens directly into your interface
  • Voice conversations — combine with Voice for spoken responses
  • Tool-using agents — combine with 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

Simple chat

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

const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });

const response = await client.agents.chat({
agent: "agent-id",
messages: [{ role: "user", content: "Hello!" }],
userId: "user-123",
language: "en",
});

console.log(response.content);

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].

for await (const event of client.agents.chatStream({
agent: "agent-id",
messages: [{ role: "user", content: "Tell me a story" }],
userId: "user-123",
language: "en",
timezone: "America/New_York",
})) {
process.stdout.write(event.choices?.[0]?.delta?.content ?? "");
}

Core concepts

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

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-<id>) so you can join conversation logs to your internal systems.

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

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

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

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

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

All chat methods live on client.agents.* (TS/Python) or client.Agents (Go).

MethodReturnsDescription
chat(opts)ChatResponseAggregated non-streaming chat
chatStream(opts)AsyncIterable<ChatStreamEvent> (TS) / iter (Py)SSE streaming — tokens as they arrive
Chat(ctx, params)*ChatResponse, errorGo aggregated chat (buffers SSE internally)
ChatStream(ctx, params, callback)errorGo SSE streaming with per-event callback
ChatStreamChannel(ctx, params)<-chan ChatStreamEvent, <-chan errorGo SSE streaming via Go channel

ChatOptions fields

FieldTypeDescription
messagesChatMessage[]Conversation history including the new user turn
userIdstringIdentifies the user; scopes per-user memory and state
sessionIdstringGroups messages into a traceable conversation
instanceIdstringIsolates state to a scenario (workspace, game session)
languagestringBCP-47 language tag for the response
timezonestringIANA timezone used for time-aware responses
maxTurnsintMaximum assistant messages per call (default: 1)
toolCapabilitiesAgentToolCapabilitiesOpt-in built-in tools (image generation, web search, …)
compiledSystemPromptstringPer-request application context injected into system prompt
provider / modelstringOverride the LLM provider or model for this call

Combines with other features

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.

// 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 — 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.

// 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 — 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.

// 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 — 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.

// 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

Next steps

  • Sessions — explicit session management and history
  • Voice — combine chat with spoken audio responses
  • Memory — how facts flow from conversation turns into long-term storage
  • Custom Tools — give the agent callable functions
  • Scheduled Reminders — proactive agent-initiated messages on a cadence

On this page