Events & Multi-Agent 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
Events
- Level-up celebrations — your game backend detects a rank change and fires a
level_upevent; the agent congratulates the user in its own voice - Daily summaries — a cron job fires a
daily_summaryevent with session stats inmetadata; 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
- 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
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.
import { Sonzai } from "@sonzai-labs/agents";
const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });
const result = await client.agents.triggerBackendEvent("agent_abc", {
userId: "user_123",
eventType: "level_up",
eventDescription: "The user just reached level 25 — a major milestone in the game.",
metadata: {
new_level: "25",
previous_level: "24",
xp_total: "12500",
},
});
console.log(result.accepted); // true
console.log(result.event_id); // "evt_01HX..."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.
import { Sonzai } from "@sonzai-labs/agents";
const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });
// Seed the conversation — agent_b opens with the first message
const messages = [
{ role: "user" as const, content: "Tell me something interesting about the ancient ruins." },
];
// Turn 1 — agent_a responds
const turnA = await client.agents.dialogue("agent_a", {
userId: "user_123",
messages,
sceneGuidance: "Two NPCs are exploring ancient ruins together. Keep responses under 3 sentences.",
});
messages.push({ role: "assistant", content: turnA.response });
// Turn 2 — agent_b responds to what agent_a said
const turnB = await client.agents.dialogue("agent_b", {
userId: "user_123",
messages,
sceneGuidance: "Two NPCs are exploring ancient ruins together. Keep responses under 3 sentences.",
});
console.log("Agent A:", turnA.response);
console.log("Agent B:", turnB.response);Core concepts
Events
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 processingevent_id(string) — an opaque identifier for the queued event; store it if you want to correlate platform logs
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 turnside_effects— optional structured metadata emitted by the agent (tool calls, mood signals, etc.)
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<string,string> / 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
With Proactive Messaging — events as the dev-controlled push source
Proactive Messaging 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.
// 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 — 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.
// 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 — 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.
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
- Medication Reminders — end-to-end example using Schedule + TriggerEvent + Memory together
- Scheduled Reminders walkthrough — covers cadence patterns that pair with events
Next steps
- Proactive Messaging — the three sources and delivery channels
- Conversations — regular agent chat mechanics
- Scheduled Reminders — recurring cadence primitive
- Evaluation — scoring agent responses