项目介绍
Agent 开发框架 · 2026-11-09 更新 · 本站尚未实测
sonzai-go 是 Sonzai Mind Layer API 的官方 Go SDK,用于构建“租户运行时”:运行时使用 Sonzai 提供的记忆与工具定义,同时由自己直接调用 LLM 服务商。按 README 描述,Sonzai Cloud 只提供记忆与工具定义,在 README 的 Client.Runtime 路径下,模型服务商凭据与配额不经过 platform-api;SDK 另有项目 BYOK 密钥登记接口,不能把前述路径的凭据边界推广到所有 API。
这是一个面向 Go 开发者的编程库(SDK),不是可直接对话的软件,也不是 MCP 服务器或 Skill 文件;需要在 Go 项目中编写代码调用。README 称其为零依赖,仅使用 Go 标准库。
核心能力
点击能力名称查看输入、输出和原文依据。
- Agent 管理:Agent 的增删改查、对话(聚合、流式、异步轮询)、上下文引擎状态查询。
- 记忆系统:记忆树、语义检索、时间线、事实写入/更新/删除/审计、批量写入、按用户重置记忆。
- 人格与上下文:Big5 人格、维度与变化记录、按用户的人格覆盖层、情绪、关系、目标、日记、兴趣等上下文检视,以及指定时间点的人格快照。
- 会话与实例:会话句柄提供逐轮富化上下文,支持并行实例用于 A/B 测试与沙箱分叉。
- 知识库:项目级文档上传、列表、删除,事实/图谱写入与跨文档语义检索。
- 语音:TTS、STT 与基于 WebSocket 的实时双向语音流(README 提到 Gemini Live)。
- 评估与模拟:评估模板、一次性评估、流式模拟、模拟与评估组合运行、重评估。
- Webhook:注册、密钥轮换、投递记录查询,并提供 HMAC-SHA256 签名校验函数。
- BYOK:为项目登记 OpenAI、Gemini、xAI、OpenRouter 的服务商密钥,调用经自备密钥计费。
实际应用场景
适用场景
适合需要在多租户产品中为 Agent 增加长期记忆、人格设定与会话状态延续能力的 Go 后端团队,例如陪伴型角色、长期对话助手、带记忆的游戏 NPC、以及对 Agent 做模拟评估的研发流程。若只需要一次性问答、不需要持久记忆与人格体系,则引入该 SDK 的必要性有限。
安装与接入
README 要求 Go 1.25 或更高版本。安装命令为:
go get github.com/sonz-ai/sonzai-go@v1.8.0
导入并使用:
import sonzai "github.com/sonz-ai/sonzai-go"
客户端构造时可传入 API Key;若首个参数为空,则读取 SONZAI_API_KEY 环境变量。可通过 WithBaseURL、WithRuntimeBaseURL、WithTimeout、WithHTTPClient 等选项覆盖默认配置,对应环境变量为 SONZAI_BASE_URL、SONZAI_RUNTIME_BASE_URL。API Key 需在 Sonzai Dashboard 的 Projects > API Keys 获取。
README 说明 GET、DELETE 等幂等请求在 429/502/503/504 时会自动重试最多 3 次并指数退避,POST/PUT/PATCH 不重试。运行时 CRM 功能需要同时配置 adapter token 与 runtime base URL。
使用条件与限制
- 是否需要部署
- 需自行准备运行环境,具体要求见正文
- 是否需要注册
- 账号和认证要求见正文;未明确说明的项目仍待核验
- 是否需要 API Key
- 待核验
- 费用与额度
- 费用边界见正文,项目许可不代表模型或托管服务免费
- 所需权限
- 待核验
- 许可证与服务条款
- 待核验
权限费用与限制
- 需自行注册 Sonzai 账号并创建 API Key;BYOK 相关接口要求 API Key 具备
read:byok/write:byok权限范围。 - 计费:README 写明标准用法按服务商成本 × 1.33 计费;使用 BYOK/BYOM 时向 Sonzai 支付 33% 服务费,因为租户直接向模型服务商付费。具体价格与结算方式请以官方为准,本文不作价格承诺。
- Client.Runtime 的模型调用由使用方自行完成;若使用项目 BYOK 登记接口则会向平台登记提供商密钥,应按对应接口说明评估凭据处理。README 示例中出现的服务商取值为 gemini、zhipu、volcengine、openrouter、custom 等,具体可用模型以官方文档为准。
- 异步对话:取消本地 context 不会取消服务端任务;README 提到 SSE 流可能被 Cloudflare/负载均衡在约 100 秒处切断,因此提供异步排队加轮询方式。
- README 中的基准测试结果(LoCoMo、LongMemEval、SOTOPIA)均为项目方自述,本文未做独立验证。
- 中国大陆网络下的可访问性、数据存储地域与合规状况,资料未说明,需向官方核验。
国内使用情况
网友核验 用户经验分享
与站长实测独立,不代表官方认证或本站已实测。
最近核验:暂无有效核验。参与人数按当前项目有效身份去重;不同核验项分别统计,不合并成用户总结果。仅统计审核通过且未过期的记录。
分项统计、有效期与样本量
| 核验项目 | 有效人数 | 成功 / 部分 / 失败 / 无法判断 | 成功率 | 最近核验 / 有效期 |
|---|---|---|---|---|
| 官方网站访问 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| GitHub 仓库访问 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| 安装与运行 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 官方服务国内连接 / MCP 连接 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 国内网络情况 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| 国内模型兼容性 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 自定义 API | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 海外服务依赖 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 海外账号要求 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 中文界面 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 客户端兼容性 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 账号及费用信息 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
成功率 = 成功 ÷(成功 + 部分成功 + 失败);无法判断不计入分母。至少 5 份可判断结果才显示成功率。账号、费用、海外依赖与语言等事实项,成功表示核查结论与资料吻合,具体观察另行统计;不表示免费或没有海外依赖。不同网络、客户端和版本的结果不能直接等同。
最近有效核验
暂无审核通过且未过期的网友核验。欢迎提交真实使用记录。
查看全部有效核验 →国内实测状态:国内可用性待核验。协议兼容和官方资料说明不等于国内网络可用。
- 官方网站访问
- 尚未实测 待核验
- GitHub 仓库访问
- 尚未实测 待核验
- 官方服务国内连接
- 尚未实测 待核验
- 国内网络情况
- 尚未实测 待核验
- 国内模型兼容性
- 尚未实测 待核验
- 自定义 API
- 尚未实测 待核验
- 海外服务依赖
- 尚未实测 待核验
- 海外账号要求
- 尚未实测 待核验
- 中文界面
- 尚未实测 待核验
- 国内最后核验时间
- 尚未实测
许可与国内使用
仓库 LICENSE 为 MIT 许可证(Copyright (c) 2026 Sonzai Labs),仅覆盖仓库中的代码。Sonzai 托管服务、平台 API、第三方模型服务及可能的商业模块的授权与费用应另行向官方确认。公开仓库采用 MIT 不代表托管服务免费或可自建部署。
国内使用方面,README 未说明服务部署地域、备案情况或对国内网络与模型的兼容性,相关事项待核验;如面向中国大陆用户提供长期记忆服务,应自行评估数据出境与个人信息保护要求。
项目资料与来源
字段依据与待核验事项
access:来源确认 · go get github.com/sonz-ai/sonzai-go@v1.8.0
project_type:来源确认 · The official Go SDK for the [Sonzai Mind Layer API](https://sonz.ai). Build tenant runtimes that use Sonzai memory while calling their LLM provider directly.
原始 README(折叠技术参考)
# Sonzai Go SDK
[](https://pkg.go.dev/github.com/sonz-ai/sonzai-go)
[](https://goreportcard.com/report/github.com/sonz-ai/sonzai-go)
[](https://opensource.org/licenses/MIT)
The official Go SDK for the [Sonzai Mind Layer API](https://sonz.ai). Build
tenant runtimes that use Sonzai memory while calling their LLM provider
directly.
Zero dependencies — Go standard library only.
## Requirements
- Go 1.25 or later
## Installation
```bash
go get github.com/sonz-ai/sonzai-go@v1.8.0
```
```go
import sonzai "github.com/sonz-ai/sonzai-go"
```
## Quick Start
```go
package main
import (
"context"
"fmt"
sonzai "github.com/sonz-ai/sonzai-go"
)
func main() {
// Reads SONZAI_API_KEY from env if the first arg is empty.
client := sonzai.MustNewClient("")
ctx := context.Background()
// Sonzai Cloud supplies memory and tool definitions only.
bundle, err := client.Runtime.ContextBundle(ctx, "your-agent-id",
sonzai.RuntimeContextBundleParams{
UserID: "user-123", SessionID: "session-123",
CurrentMessage: "Hello!",
})
if err != nil { panic(err) }
// Invoke OpenAI, Gemini, OpenRouter, or your own endpoint here, from this
// runtime. Provider credentials and quota never pass through platform-api.
fmt.Printf("context ready: %d system parts\n", len(bundle.SystemPromptParts))
}
```
`Client.Runtime` intentionally has no completion method. After a direct
provider call, use `ReportTurns` to persist the transcript and
`SignRuntimeUsageReport` + `SubmitUsageReport` for attributed billing. Standard
usage is benchmark provider cost × 1.33; BYOK/BYOM usage is a 33% Sonzai
service fee because the tenant pays its provider directly.
See the [examples/](examples/) directory for complete, runnable programs covering chat, agent lifecycle, memory, voice, and evaluation.
> **Using the OpenClaw plugin?** Your API key is stored in `openclaw.json` — no environment variables needed. See the [@sonzai-labs/openclaw-context](../sonzai-openclaw/) docs.
## Client & Configuration
```go
// Constructor with API key (falls back to SONZAI_API_KEY env var if empty)
client, err := sonzai.NewClient("")
// Or panic-on-error variant (for CLI tools and tests)
client := sonzai.MustNewClient("")
// Options
client := sonzai.MustNewClient("",
sonzai.WithBaseURL("https://api.sonz.ai"), // or SONZAI_BASE_URL env var
sonzai.WithRuntimeBaseURL("https://runtime.example.com"), // or SONZAI_RUNTIME_BASE_URL env var
sonzai.WithTimeout(60*time.Second), // default 30s
sonzai.WithHTTPClient(&http.Client{...}), // custom transport / proxy / mTLS
)
```
Idempotent requests (GET, DELETE) automatically retry up to 3 times with exponential backoff on 429/502/503/504. POST/PUT/PATCH are not retried.
## Authentication
Get your API key from the [Sonzai Dashboard](https://platform.sonz.ai) under **Projects > API Keys**. Set it via argument or environment variable:
```bash
export SONZAI_API_KEY=sk-...
```
## Resources
The `Client` exposes these top-level resource groups:
| Resource | Purpose |
|---|---|
| `Agents` | Agent CRUD, chat, dialogue, context engine state |
| `Knowledge` | Project-scoped knowledge base (docs, graph, facts, search) |
| `Eval` | Evaluations, simulations, eval runs, templates (sub-package) |
| `Voices` | Global voice catalog |
| `Webhooks` | Webhook registration, rotation, delivery inspection |
| `ProjectConfig` / `AccountConfig` | Scoped key-value configuration |
| `CustomLLM` | Bring-your-own-model (BYOM) configuration |
| `ProjectNotifications` | Project notification polling |
| `Tenants` | Multi-tenant lookup |
| `APIKeys` | Project API key management |
| `Analytics` | Cost, usage, and real-time analytics |
| `UserPersonas` | User persona CRUD |
| `Storefront` | Agent marketplace publishing |
| `Org` | Organization billing and usage |
| `Workbench` | Internal simulation and debugging |
| `Support` | Support tickets |
| `Crm` | Runtime-local CRM adapter import and event feed |
Sub-resources on `client.Agents`:
```go
client.Agents.Memory // memory tree, search, facts, timeline
client.Agents.Personality // Big5, dimensions, deltas, overlays
client.Agents.Sessions // session lifecycle
client.Agents.Instances // parallel agent instances
client.Agents.Notifications // proactive notifications
client.Agents.CustomState // scoped key-value state
client.Agents.Image // image generation
client.Agents.Voice // TTS, STT, live WebSocket streaming
client.Agents.Wakeups // wakeup scheduling
client.Agents.Generation // bio, character, seed memory generation
client.Agents.Priming // user priming & batch import
client.Agents.Inventory // user inventory
client.Agents.Schedules // user-scoped recurring events
```
## Runtime CRM
The runtime CRM is served by a deployed `app-runtime` instance under
`/api/rt/crm/*`, not by the Sonzai platform API. Configure both the adapter
token and runtime base URL before using `client.Crm`:
```go
client := sonzai.MustNewClient("adapter-token",
sonzai.WithRuntimeBaseURL("https://runtime.example.com"),
)
result, err := client.Crm.Import(ctx, []sonzai.CrmImportItem{{
ExternalRef: "sf-123",
FirstName: "Grace",
LastName: "Hopper",
Emails: json.RawMessage(`["grace@example.com"]`),
}}, &sonzai.CrmImportOptions{TenantID: "tenant-a"})
events, err := client.Crm.Events(ctx, &sonzai.CrmEventsOptions{
Cursor: savedCursor,
Limit: 100,
TenantID: "tenant-a",
})
savedCursor = events.NextCursor
```
Only the adapter-token surface is exposed: bulk contact import and the cursor
event feed. Staff CRM CRUD routes are browser-session routes in the runtime and
are not part of the headless SDK surface.
## Bring Your Own Key (BYOK)
BYOK lets you register your own LLM provider API keys with a project. Once set,
upstream LLM calls for that project route through your key — token billing falls
on your provider account, not Sonzai's. Keys are encrypted at rest server-side
and are never returned by the API (only the key prefix and health metadata are
exposed). Requires `read:byok` / `write:byok` scopes on the API key you use to
call these endpoints.
```go
ctx := context.Background()
// List all configured BYOK providers for a project
keys, _ := client.BYOK.List(ctx, "project-id")
for _, k := range keys {
fmt.Printf("%s: %s (active: %v)\n", k.Provider, k.HealthStatus, k.IsActive)
}
// Store or replace a key (validated against the provider before saving)
key, _ := client.BYOK.Set(ctx, "project-id", sonzai.BYOKProviderOpenAI, "sk-...")
fmt.Println("Stored prefix:", key.APIKeyPrefix)
// Enable or disable without rotating
client.BYOK.SetActive(ctx, "project-id", sonzai.BYOKProviderOpenAI, false)
// Re-run the provider health check on a stored key
result, _ := client.BYOK.Test(ctx, "project-id", sonzai.BYOKProviderGemini)
fmt.Println(result.HealthStatus) // "healthy" | "invalid" | "unknown"
// Remove a stored key (project falls back to platform billing)
client.BYOK.Delete(ctx, "project-id", sonzai.BYOKProviderXAI)
```
Provider constants: `sonzai.BYOKProviderOpenAI`, `sonzai.BYOKProviderGemini`,
`sonzai.BYOKProviderXAI`, `sonzai.BYOKProviderOpenRouter`.
REST path: `/api/v1/projects/{project_id}/byok-keys[/{provider}[/test]]`.
## Usage
### Agent CRUD
```go
agent, _ := client.Agents.Create(ctx, sonzai.CreateAgentOptions{
Name: "Aria",
Gender: "female",
Bio: "A curious explorer who loves stargazing.",
Big5: &sonzai.Big5Scores{
Openness: 85, Conscientiousness: 60, Extraversion: 70,
Agreeableness: 75, Neuroticism: 25,
},
// Tool capabilities are configurable at creation time:
ToolCapabilities: &sonzai.AgentToolCapabilities{
WebSearch: true,
RememberName: true,
KnowledgeBase: sonzai.Ptr(true), // enable project-scoped KB search
MemoryMode: "async", // "sync" (default) or "async"
},
})
fetched, _ := client.Agents.Get(ctx, agent.AgentID)
agents, _ := client.Agents.List(ctx, sonzai.ListAgentsOptions{PageSize: 20})
client.Agents.Delete(ctx, agent.AgentID)
```
### Chat (aggregated response)
```go
resp, _ := client.Agents.Chat(ctx, sonzai.AgentChatParams{
AgentID: "agent-id",
ChatOptions: sonzai.ChatOptions{
Messages: []sonzai.ChatMessage{
{Role: "user", Content: "Tell me about yourself"},
},
UserID: "user-123",
SessionID: "session-456", // auto-created if omitted
Provider: "gemini",
Model: "gemini-3.1-flash-lite",
},
})
fmt.Println(resp.Content)
fmt.Println(resp.Usage.TotalTokens)
```
### Chat (streaming)
Two streaming patterns — callback and channel.
```go
// Callback
err := client.Agents.ChatStream(ctx, params, func(event sonzai.ChatStreamEvent) error {
fmt.Print(event.Content())
return nil
})
// Channel
events, errCh := client.Agents.ChatStreamChannel(ctx, params)
for event := range events {
fmt.Print(event.Content())
}
if err := <-errCh; err != nil {
log.Fatal(err)
}
```
`ChatStreamEvent` exposes `Content()`, `IsFinished()`, `Usage`, `FinishReason`, `ExternalToolCalls`, and `SideEffectsJSON` (facts, emotions, relationship updates).
### Chat (async with polling)
For chats that may run longer than your network can hold an SSE stream
open (Cloudflare/LB cuts at ~100s), queue the request and poll the
result endpoint. Cancelling the local context does **not** cancel the
server-side task — re-poll the same `processing_id` later if needed.
```go
// Fire-and-forget — returns ProcessingID immediately.
queued, _ := client.Agents.ChatAsync(ctx, sonzai.AgentChatParams{
AgentID: "agent-id",
ChatOptions: sonzai.ChatOptions{
Messages: []sonzai.ChatMessage{{Role: "user", Content: "Plan my week."}},
UserID: "user-123",
Provider: "openai",
Model: "gpt-4o",
},
})
// Manual poll loop — recommended backoff: 1s → 2s → 4s, capped at 5s.
delay := time.Second
for {
result, _ := client.Agents.PollChatResult(ctx, "agent-id", queued.ProcessingID)
// Status: queued | running | complete | failed.
// While running, Response carries partial assistant text and
// Phase/Tool reflect the latest progressive-elaboration event.
if result.IsTerminal() {
fmt.Println(result.Response, string(result.SideEffects))
break
}
time.Sleep(delay)
if delay < 5*time.Second {
delay *= 2
}
}
// Convenience: queue + poll until terminal in one call.
result, err := client.Agents.ChatAsyncBlocking(ctx, sonzai.AgentChatParams{
AgentID: "agent-id",
ChatOptions: sonzai.ChatOptions{
Messages: []sonzai.ChatMessage{{Role: "user", Content: "Plan my week."}},
UserID: "user-123",
},
})
```
### Sync vs async memory recall (`memoryMode`)
Supplementary memory recall can be **synchronous** (blocks on recall, facts always land in the current turn) or **asynchronous** (races a deadline, slow hits spill to the next turn for lower first-token latency). Default is `sync`.
`memoryMode` is an agent-wide capability. You can set it at creation time or flip it later with `UpdateCapabilities`:
```go
// At creation
client.Agents.Create(ctx, sonzai.CreateAgentOptions{
Name: "Aria",
ToolCapabilities: &sonzai.AgentToolCapabilities{
MemoryMode: "async",
},
})
// Or flip an existing agent
client.Agents.UpdateCapabilities(ctx, "agent-id", sonzai.UpdateCapabilitiesOptions{
MemoryMode: "async", // or "sync"
})
// Read the current mode
caps, _ := client.Agents.GetCapabilities(ctx, "agent-id")
fmt.Println(caps.MemoryMode)
```
`UpdateCapabilities` is PATCH-style — omitted fields are left unchanged. To skip the context engine entirely on a single chat (e.g. test paths), set `SkipContextBuild: true` in the chat options.
### Advanced chat options
```go
ChatOptions{
Messages: []sonzai.ChatMessage{...},
UserID: "user-123",
UserDisplayName: "Alex",
SessionID: "session-456",
InstanceID: "instance-789", // parallel branch
Provider: "gemini", // gemini | zhipu | volcengine | openrouter | custom
Model: "gemini-3.1-flash-lite",
Language: "en",
Timezone: "America/New_York",
CompiledSystemPrompt: "You are a helpful assistant.",
ToolCapabilities: &sonzai.AgentToolCapabilities{WebSearch: true, RememberName: true},
ToolDefinitions: []sonzai.ToolDefinition{ /* external function calls */ },
MaxTurns: 10,
GameContext: &sonzai.GameContext{ /* custom fields */ },
SkillLevels: map[string]int32{"negotiation": 5},
}
```
### Memory
```go
// Full memory tree
tree, _ := client.Agents.Memory.List(ctx, "agent-id", &sonzai.MemoryListOptions{
UserID: "user-123",
IncludeContents: true,
Limit: 100,
})
for _, node := range tree.Nodes {
fmt.Printf("%s (importance: %.2f)\n", node.Title, node.Importance)
}
// Semantic search (cosine over fact embeddings when UserID is set)
results, _ := client.Agents.Memory.Search(ctx, "agent-id", sonzai.MemorySearchOptions{
Query: "favorite food",
UserID: "user-123",
Limit: 10,
})
// Timeline across a date range
timeline, _ := client.Agents.Memory.Timeline(ctx, "agent-id", &sonzai.MemoryTimelineOptions{
UserID: "user-123",
Start: "2026-01-01",
End: "2026-03-01",
})
// Write a fact directly
fact, _ := client.Agents.Memory.CreateFact(ctx, "agent-id", sonzai.CreateFactOptions{
UserID: "user-123",
Content: "The user loves pizza",
FactType: "preference",
Importance: 0.8,
Entities: []string{"pizza"},
})
// Update, delete, audit
client.Agents.Memory.UpdateFact(ctx, "agent-id", fact.FactID, sonzai.UpdateFactOptions{Importance: 0.9})
history, _ := client.Agents.Memory.GetFactHistory(ctx, "agent-id", fact.FactID)
client.Agents.Memory.DeleteFact(ctx, "agent-id", fact.FactID)
// Wisdom (agent-global) facts
client.Agents.Memory.GetWisdomAudit(ctx, "agent-id", fact.FactID)
client.Agents.Memory.DeleteWisdomFact(ctx, "agent-id", fact.FactID)
// Bulk create up to 1000 pre-formed facts (source_type="manual", no LLM extraction)
client.Agents.Memory.BulkCreateFacts(ctx, "agent-id", sonzai.BulkCreateFactsOptions{
UserID: "user-123",
Facts: []sonzai.BulkFactItem{
{Content: "prefers espresso"},
{Content: "based in Singapore", FactType: "location"},
},
})
// Seed memories in bulk at agent creation
client.Agents.Memory.Seed(ctx, "agent-id", sonzai.SeedMemoriesOptions{
UserID: "user-123",
Memories: []sonzai.SeedMemory{ /* ... */ },
})
// Reset all memory for a user
client.Agents.Memory.Reset(ctx, "agent-id", sonzai.MemoryResetOptions{UserID: "user-123"})
```
### Personality
```go
profile, _ := client.Agents.Personality.Get(ctx, "agent-id")
fmt.Println(profile.Profile.Big5.Openness)
fmt.Println(profile.Profile.Dimensions.Warmth)
// Recent personality shifts and significant moments
shifts, _ := client.Agents.Personality.GetRecentShifts(ctx, "agent-id")
moments, _ := client.Agents.Personality.GetSignificantMoments(ctx, "agent-id", 10)
// Per-user overlays (how the agent perceives a specific user)
overlays, _ := client.Agents.Personality.ListUserOverlays(ctx, "agent-id")
overlay, _ := client.Agents.Personality.GetUserOverlay(ctx, "agent-id", "user-123", sonzai.UserOverlayOptions{})
```
### Sessions & instances
`Sessions.Start` returns a `*Session` handle that bundles the identity tuple
(`AgentID`, `UserID`, `SessionID`, `InstanceID`) plus session-level
`Provider`/`Model` defaults. The handle drives the per-turn loop with one
fresh enriched context per turn — so the LLM you call out to (OpenAI,
Anthropic, Gemini, your own) sees an up-to-date mood, recalled facts,
and recent turns on every message.
```go
session, _ := client.Agents.Sessions.Start(ctx, "agent-id", sonzai.SessionStartOptions{
UserID: "user-123",
SessionID: "session-456",
Provider: "gemini", // session-level default
Model: "gemini-3.1-flash-lite", // (per-turn overrides OK)
})
// Per-turn loop: fetch enriched context, hand it to your LLM, submit the turn.
enriched, _ := session.Context(ctx, sonzai.ContextOptions{
Query: "what's the user about to say?",
})
// ... build your prompt with `enriched`, call your LLM, get assistantReply ...
result, _ := session.Turn(ctx, sonzai.TurnOptions{
Messages: []sonzai.TurnMessage{
{Role: "user", Content: "what did we talk about last week?"},
{Role: "assistant", Content: assistantReply},
},
// Prefetch the *next* enriched context in the same round-trip
// so the next user message renders without a second fetch.
FetchNextContext: &sonzai.FetchNextContext{Query: "anticipated next user message"},
})
fmt.Println(result.Mood) // sync mood update (nil if none)
fmt.Println(result.ExtractionID) // async fact-extraction job id
fmt.Println(result.NextContext) // populated when FetchNextContext is set
// Poll deferred extraction (memory write-back) when you need to know it landed.
for {
status, _ := session.TurnStatus(ctx, result.ExtractionID)
if status.IsTerminal() { // "done" or "failed"
break
}
time.Sleep(500 * time.Millisecond) // then back off
}
// End the session. Wait=true forces the CE pipeline to run synchronously
// (use in benchmarks/tests that query memory immediately after).
_ = session.End(ctx, sonzai.SessionEndOptions{
TotalMessages: 10,
DurationSeconds: 300,
Wait: true,
})
```
`TurnMessage` carries `ToolCallID` and `ToolCalls` for function-calling
turns; plain `ChatMessage` (used by `Chat` / `ChatStream`) is text-only.
Per-call `Provider`/`Model` on `TurnOptions` override the session
defaults; leave both empty to fall through.
#### Legacy void-style start/end
`Sessions.End` still accepts a void-style call when you don't need the
handle:
```go
client.Agents.Sessions.End(ctx, "agent-id", sonzai.SessionEndOptions{
UserID: "user-123",
SessionID: "session-456",
TotalMessages: 10,
DurationSeconds: 300,
})
```
#### Instances
```go
// Parallel agent instances for A/B testing or sandboxed forks
instance, _ := client.Agents.Instances.Create(ctx, "agent-id", sonzai.InstanceCreateOptions{Name: "Beta"})
client.Agents.Instances.Reset(ctx, "agent-id", instance.InstanceID)
client.Agents.Instances.Delete(ctx, "agent-id", instance.InstanceID)
```
### Context engine introspection
```go
mood, _ := client.Agents.GetMood(ctx, "agent-id", "user-123", "")
relationships, _ := client.Agents.GetRelationships(ctx, "agent-id", "user-123", "")
habits, _ := client.Agents.ListHabits(ctx, "agent-id", "user-123", "")
goals, _ := client.Agents.ListGoals(ctx, "agent-id", "user-123", "")
interests, _ := client.Agents.GetInterests(ctx, "agent-id", "user-123", "")
diary, _ := client.Agents.GetDiary(ctx, "agent-id", "user-123", "")
users, _ := client.Agents.GetUsers(ctx, "agent-id", nil)
breakthroughs, _ := client.Agents.GetBreakthroughs(ctx, "agent-id", "user-123", "")
// Single-call enriched context (memory, personality, mood, relationships, goals,
// plus recent_turns from this session for mid-session recall before consolidation runs)
enriched, _ := client.Agents.GetContext(ctx, "agent-id", sonzai.GetContextOptions{
UserID: "user-123",
Query: "what did we discuss earlier about espresso?",
})
for _, t := range enriched.RecentTurns {
fmt.Printf("[%s] %s: %s\n", t.Timestamp, t.Role, t.Content)
}
// Point-in-time personality snapshot
snapshot, _ := client.Agents.GetTimeMachine(ctx, "agent-id", sonzai.TimeMachineOptions{
At: "2026-01-15T00:00:00Z",
UserID: "user-123",
})
```
### Custom state (scoped key-value)
```go
state, _ := client.Agents.CustomState.Create(ctx, "agent-id", sonzai.CustomStateCreateOptions{
Key: "player_level",
Value: map[string]any{"level": 15, "xp": 2400},
Scope: "user", // or "global"
ContentType: "json",
UserID: "user-123",
})
// Upsert by composite key
client.Agents.CustomState.Upsert(ctx, "agent-id", sonzai.CustomStateUpsertOptions{
Key: "player_level", Scope: "user", UserID: "user-123",
Value: map[string]any{"level": 16},
})
client.Agents.CustomState.GetByKey(ctx, "agent-id", sonzai.CustomStateGetByKeyOptions{
Key: "player_level", Scope: "user", UserID: "user-123",
})
client.Agents.CustomState.DeleteByKey(ctx, "agent-id", sonzai.CustomStateDeleteByKeyOptions{
Key: "player_level", Scope: "user", UserID: "user-123",
})
```
### Notifications
```go
pending, _ := client.Agents.Notifications.List(ctx, "agent-id", sonzai.NotificationListOptions{
Status: "pending",
UserID: "user-123",
Limit: 20,
})
for _, n := range pending.Notifications {
fmt.Printf("[%s] %s\n", n.CheckType, n.GeneratedMessage)
}
client.Agents.Notifications.Consume(ctx, "agent-id", "message-id")
history, _ := client.Agents.Notifications.History(ctx, "agent-id", 50)
```
### Knowledge base
```go
// Upload a document (PDF, markdown, etc.)
file, _ := os.Open("document.pdf")
defer file.Close()
doc, _ := client.Knowledge.UploadDocument(ctx, "project-id", "document.pdf", file, "application/pdf")
docs, _ := client.Knowledge.ListDocuments(ctx, "project-id", 100)
client.Knowledge.GetDocument(ctx, "project-id", doc.DocumentID)
client.Knowledge.DeleteDocument(ctx, "project-id", doc.DocumentID)
// Direct fact/graph insertion
client.Knowledge.InsertFacts(ctx, "project-id", sonzai.InsertFactsOptions{
Source: "manual",
Facts: []sonzai.InsertFactEntry{
{EntityType: "person", Label: "Alice"},
},
Relationships: []sonzai.InsertRelEntry{
{FromLabel: "Alice", ToLabel: "Bob", EdgeType: "knows"},
},
})
// Cross-document semantic search
results, _ := client.Knowledge.Search(ctx, "project-id", sonzai.KBSearchOptions{
Query: "how do I configure X?",
Limit: 10,
})
for _, r := range results.Results {
fmt.Printf("[%.2f] %s (type: %s)\n", r.Score, r.Label, r.NodeType)
}
```
### Voice (TTS, STT, live streaming)
```go
// Text-to-Speech
audio, _ := client.Agents.Voice.TTS(ctx, "agent-id", sonzai.TTSOptions{
Text: "Hello, how are you?",
VoiceName: "Kore",
Language: "en-US",
})
// Speech-to-Text
transcript, _ := client.Agents.Voice.STT(ctx, "agent-id", sonzai.STTOptions{
Audio: base64.StdEncoding.EncodeToString(pcmBytes),
AudioFormat: "pcm",
Language: "en-US",
})
// Live bidirectional voice (WebSocket, Gemini Live)
token, _ := client.Agents.Voice.GetToken(ctx, "agent-id", sonzai.VoiceTokenOptions{
VoiceName: "Kore",
Language: "en-US",
UserID: "user-123",
})
stream, _ := client.Agents.Voice.Stream(ctx, token)
defer stream.Close()
stream.SendText("Hello!")
// or: stream.SendAudio(pcm16kHzMonoBytes)
for {
event, err := stream.Recv()
if err == io.EOF { break }
switch event.Type {
case "input_transcript":
fmt.Println("User:", event.Text)
case "output_transcript":
fmt.Println("Agent:", event.Text)
case "audio":
playPCM(event.Audio) // 24kHz PCM
case "turn_complete":
case "session_ended":
return
}
}
```
### Evaluation & simulation
```go
import "github.com/sonz-ai/sonzai-go/eval"
// One-off evaluation over a conversation
result, _ := client.Eval.Evaluate(ctx, "agent-id", eval.EvaluateOptions{
Messages: []eval.Message{{Role: "user", Content: "Hello"}},
TemplateID: "template-uuid",
})
fmt.Printf("Score: %.2f\n", result.Score)
// Streaming simulation
client.Eval.Simulate(ctx, "agent-id", eval.SimulateOptions{
UserPersona: map[string]any{"name": "Alex", "background": "Student"},
Config: map[string]any{"max_sessions": 2, "max_turns_per_session": 5},
}, func(event eval.SimulationEvent) error {
fmt.Printf("[%s] %s\n", event.Type, event.Message)
return nil
})
// Fire-and-forget
ref, _ := client.Eval.SimulateAsync(ctx, "agent-id", eval.SimulateOptions{
Config: map[string]any{"max_sessions": 2},
})
// Reconnect later (from_index supports resume)
client.Eval.Runs.StreamEvents(ctx, ref.RunID, 0, func(event eval.SimulationEvent) error {
fmt.Printf("[%s] %s\n", event.Type, event.Message)
return nil
})
// Combined simulation + evaluation
client.Eval.Run(ctx, "agent-id", eval.RunEvalOptions{
TemplateID: "template-uuid",
SimulationConfig: map[string]any{"max_sessions": 2},
}, handler)
// Re-evaluate an existing run with a different template
client.Eval.ReEval(ctx, "agent-id", eval.ReEvalOptions{
TemplateID: "new-template-uuid",
SourceRunID: "existing-run-uuid",
}, handler)
// Template & run management
tmpl, _ := client.Eval.Templates.Create(ctx, eval.CreateTemplateOptions{Name: "Empathy", ScoringRubric: "..."})
runs, _ := client.Eval.Runs.List(ctx, eval.ListRunsOptions{AgentID: "agent-id", Limit: 20})
run, _ := client.Eval.Runs.Get(ctx, "run-id")
client.Eval.Runs.Delete(ctx, "run-id")
```
### Webhooks
```go
// Register/update a webhook
resp, _ := client.Webhooks.Register(ctx, "agent.message.created", sonzai.WebhookRegisterOptions{
WebhookURL: "https://example.com/hook",
AuthHeader: "Bearer your-secret", // optional header added to deliveries
})
fmt.Println("signing secret:", resp.SigningSecret)
list, _ := client.Webhooks.List(ctx)
attempts, _ := client.Webhooks.ListDeliveryAttempts(ctx, "agent.message.created")
rotated, _ := client.Webhooks.RotateSecret(ctx, "agent.message.created")
client.Webhooks.Delete(ctx, "agent.message.created")
// Project-scoped variants
client.Webhooks.RegisterForProject(ctx, "project-id", "event.type", opts)
client.Webhooks.ListForProject(ctx, "project-id")
client.Webhooks.DeleteForProject(ctx, "project-id", "event.type")
```
### Webhook signature verification
Signatures are HMAC-SHA256 over `"{timestamp}.{payload}"`. Multiple `v1=` signatures are supported for secret rotation.
```go
func handler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
sig := r.Header.Get("Sonzai-Signature")
if err := sonzai.VerifyWebhookSignature(body, sig, webhookSecret); err != nil {
switch {
case errors.Is(err, sonzai.ErrInvalidSignature):
http.Error(w, "bad signature", 401)
case errors.Is(err, sonzai.ErrTimestampExpired):
http.Error(w, "expired", 401)
default:
http.Error(w, "unauthorized", 401)
}
return
}
// Accept tighter/looser timestamp windows:
sonzai.VerifyWebhookSignatureWithTolerance(body, sig, secret, 1*time.Minute)
}
```
## Error handling
```go
resp, err := client.Agents.Chat(ctx, params)
if err != nil {
var (
authErr *sonzai.AuthenticationError
notFoundErr *sonzai.NotFoundError
badReqErr *sonzai.BadRequestError
permErr *sonzai.PermissionDeniedError
rateErr *sonzai.RateLimitError
serverErr *sonzai.InternalServerError
)
switch {
case errors.As(err, &authErr):
log.Fatal("invalid API key")
case errors.As(err, ¬FoundErr):
log.Fatal("agent not found")
case errors.As(err, &rateErr):
if rateErr.RetryAfter != nil {
log.Printf("rate limited, retry after %ds", *rateErr.RetryAfter)
}
case errors.As(err, &serverErr):
log.Fatal("server error")
}
}
```
All typed errors embed `sonzai.SonzaiError` (exposes `StatusCode` and `Message`).
## Benchmarks
Sonzai leads on **three** independent benchmarks (LoCoMo, LongMemEval,
SOTOPIA), running on the cheap end of the LLM stack — chat, judge, reader,
and partner agent all run on **Gemini 3.1 Flash Lite**. No frontier-model
arms race propping up the numbers; the lift is from the memory architecture.
### LoCoMo — long-term conversational memory (mem0's home turf)
10 peer-to-peer dialogues, 19–35 sessions each, 1540 QAs across 4 reasoning
categories. Run via mem0's published evaluation pipeline byte-for-byte
(their `ANSWER_PROMPT` + `ACCURACY_PROMPT`, dual-perspective ingest, dual
search) so numbers are directly comparable.
| Category | n | Sonzai (J) | mem0 (J, published) |
|---|---:|---:|---:|
| 1. single-hop | 282 | **0.720** | ~0.65 |
| 2. multi-hop | 321 | **0.723** | ~0.55 |
| 3. temporal reasoning | 96 | 0.531 | ~0.55 |
| 4. open-domain | 841 | **0.762** | ~0.71 |
| **Overall** | 1540 | **0.732** ✅ | ~0.67 |
Multi-hop is Sonzai's strongest category (**+~17 points** over mem0) — the
hardest LoCoMo bucket and the one mem0's graph variant typically claims its
lift on. Sonzai matches/beats without graph-specific machinery.
### LongMemEval — retrieval (MemPalace's home turf)
Sonzai beats MemPalace on the retrieval benchmark MemPalace was purpose-built to win:
| Metric | Sonzai | MemPalace (hybrid_v4) |
|---|---:|---:|
| R@G (overall recall) | **0.773** | 0.741 |
| R@1 (top-hit accuracy) | **0.800** | 0.770 |
| Recall@10, multi-session | **1.000** | 1.000 |
### SOTOPIA longitudinal — compounding across sessions
Sonzai's USP: agents that compound. Same agent, same partner, N sessions,
`advance_time` between each. Across 30 sessions Overall climbs from 8.44 → 9.56
(+1.13), and `memory_continuity` hits the rubric ceiling by session 10 — Sonzai's
identity model is producing accurate unprompted callbacks before a verbatim-retrieval
baseline has history to compete.
Full scores, methodology, per-question-type breakdown, and reproduction steps:
→ [sonzai-python/benchmarks/README.md](https://github.com/sonz-ai/sonzai-python/blob/main/benchmarks/README.md)
## Staying in sync with the production API
This SDK tracks `https://api.sonz.ai/docs/openapi.json`. A git pre-push hook
checks for drift; run `just install-hooks` once after cloning. To refresh the
committed spec snapshot, run `just sync-spec` and commit the diff.
## Documentation
- [Sonzai docs](https://sonz.ai/docs)
- [API reference](api.md)
- [Package docs on pkg.go.dev](https://pkg.go.dev/github.com/sonz-ai/sonzai-go)
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## License
MIT — see [LICENSE](LICENSE) for details.
历史前置条件说明(补充参考)
Go 1.25 或更高;需在 Sonzai Dashboard 创建 API Key,可通过 SONZAI_API_KEY 环境变量或构造参数传入;BYOK 接口需 read:byok / write:byok 权限范围;模型服务商密钥与配额由使用方自备并直连;Sonzai 平台计费标准与 BYOK 服务费以官方为准,未经确认的价格不在此说明;托管服务可用区域、国内网络可达性与数据存储地域待核验。
项目来源与清单版权许可
https://github.com/sonz-ai/sonzai-go
整理来源:https://github.com/slavakurilyak/awesome-ai-agents
清单版权与许可:
MIT License
Copyright (c) 2024 Slava Kurilyak
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.