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).
| Method | Returns | Description |
|---|---|---|
chat(opts) | ChatResponse | Aggregated non-streaming chat |
chatStream(opts) | AsyncIterable<ChatStreamEvent> (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
| 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
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
- Getting started — first chat call end-to-end
- Quickstart — five-minute TypeScript, Python, or Go setup
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