---
title: "Session Loop · Finch Agent"
description: "创建会话、发消息、拿结果与多 Agent 编排"
source: https://finchwork.app/zh/docs/minitools-sessions
---

# Session Loop

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

**隔离边界：小程序只能访问自己创建的 Session**，读不到用户的普通对话，也碰不到别的小程序的会话。

## 完整回路

## 创建会话

```ts
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 一条支线。在工具调用之外使用会抛错。

带 `initialMessage` 的 `create()` 失败时不会留下幽灵会话。

## 发消息

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

```ts
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 })` | 只用于**历史回溯和断线恢复** |

```ts
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.message` 或 `turn.completed`。事件保留 7 天，每个小程序上限 10000 条。

**禁止 `sleep` + 轮询 `listEvents()` 的写法**，该用 `waitForTurn()`。

## 编排范式：Planner → Worker → Writer

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

```ts
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.sessions` 或 `contributes.sessionContainers`。
-   传了未在 manifest 声明的 `containerId`。
-   省略 `idempotencyKey`，外部 webhook 每次重投都新建一轮。
-   在工具调用之外用 `context: 'caller'`。
-   以为 `assistant.delta` 会持久化。
-   不处理 `send()` 的 `rejected` 队列满状态。
-   每条外部消息都新建会话，而不是一个联系人复用一个会话。
