Skip to main content

Session Loop

创建会话、发消息、拿结果与多 Agent 编排

ctx.sessions 让小工具创建并驱动自己的会话。典型用途:外部平台 Bot、后台任务处理、多 Agent 编排。

隔离边界:小工具只能访问自己创建的 Session,读不到用户的普通对话,也碰不到别的小工具的会话。

完整回路

创建会话

const session = await ctx.sessions.create({
  containerId: 'inbox',            // 容器投放
  title: 'Chat with Alice',
  activity: 'interactive',         // 或 'background'
  permissionMode: 'ask',           // interactive 默认 ask;background 默认 acceptCalls
  initialMessage: {
    text: 'Hello from the bot.',
    idempotencyKey: 'welcome-alice-2026-01-01',
  },
});

投放位置二选一,不能混用:

  • containerId — 投放到自己的容器(小工具默认选择)。
  • space: { spaceId } — 投放到普通 Space 会话列表,看起来像用户自己建的,所有权仍属小工具。

activity 的实际差别background 会话完成或等待时不弹系统通知,只在容器入口显示红点提醒,权限默认 acceptCalls,适合无人值守任务;interactive 是正常聊天会话。

context: 'caller' 只能在 Agent 工具的 execute() 内使用,让新会话继承调用方的 cwd、模型、策略和 Space 上下文,适合从当前对话 fork 一条支线。在工具调用之外使用会抛错。

initialMessagecreate() 失败时不会留下幽灵会话。

发消息

send() 在单个会话内严格 FIFO:

const receipt = await ctx.sessions.send(session.sessionId, {
  text: 'What is the weather?',
  idempotencyKey: 'msg-123',       // 必填
});

if (receipt.state === 'rejected') {
  // 队列满,等 receipt.retryAfterMs 后重试,不要立刻重灌
}

idempotencyKey 必填,最长 512 字符。应该用外部来源的稳定消息 ID,不要用随机 UUID——重复发送时会返回原始回执而不是新建一轮。

限制:文本 10 万字符;附件每条 10 个、单个 20MB、总计 20MB。

拿结果:三个 API 各司其职

API用途
waitForTurn(sessionId, turnId)当前操作需要这一轮的确切最终结果,请求-响应式编排
onDidReceiveEvent(cb)跨多个会话、多轮的长期观察,Bot 场景
listEvents({ sessionId, after })只用于历史回溯和断线恢复
const result = await ctx.sessions.waitForTurn(
  sessionId, receipt.turnId, { timeoutMs: 60_000 },
);
if (result.state === 'completed') console.log(result.outputText);
if (result.state === 'failed')    console.error(result.code);
if (result.state === 'timeout')   console.log('仍在运行');

超时默认 60 秒,钳制在 1–600 秒。超时只结束这次等待,不会取消那一轮执行。

事件类型中 assistant.delta实时流式片段,不持久化;断线恢复必须依赖 assistant.messageturn.completed。事件保留 7 天,每个小工具上限 10000 条。

禁止 sleep + 轮询 listEvents() 的写法,该用 waitForTurn()

编排范式:Planner → Worker → Writer

多 Agent 编排的标准形态:主工具在一次调用里拆解任务、并行开子会话、等待全部结果、汇总输出。

const results = await Promise.all(tasks.map(async (task, i) => {
  const s = await ctx.sessions.create({
    containerId: 'workers',
    activity: 'background',
    context: 'caller',
    initialMessage: { text: task, idempotencyKey: `job-${jobId}-${i}` },
  });
  const r = await ctx.sessions.waitForTurn(s.sessionId, s.turnId, { timeoutMs: 300_000 });
  return r.state === 'completed' ? r.outputText : `任务 ${i} 失败`;
}));

background 让子会话不打扰用户,用户仍可在容器里点进去观察每个子会话的完整过程。

配额

限制数值
单会话未完成轮次20
单小工具未完成轮次200
队列满重试间隔1000 ms
事件保留数 / 时长10000 条 / 7 天

常见错误

  • 漏声明 permissions.sessionscontributes.sessionContainers
  • 传了未在 manifest 声明的 containerId
  • 省略 idempotencyKey,外部 webhook 每次重投都新建一轮。
  • 在工具调用之外用 context: 'caller'
  • 以为 assistant.delta 会持久化。
  • 不处理 send()rejected 队列满状态。
  • 每条外部消息都新建会话,而不是一个联系人复用一个会话。