# Suna / Kortix 源码分析：把公司当成一个 Git 仓库来跑的 AI Command Center

> **分析对象**：[kortix-ai/suna](https://github.com/kortix-ai/suna)（产品名 **Kortix**；仓名历史遗留仍叫 Suna）
> **基于 commit**：`5862e95671cfd198c6a94ceb9c1fefe579f00a8b`（完整检出加深版；`git rev-parse HEAD`）
> **版本**：根目录 `VERSION` 为 `0.10.14`；README badge 仍写 `0.9.98`（`README.md:12`）——**以 `VERSION` 文件为准**
> **分析日期**：2026-07-24
> **文档版本**：**完整检出加深版**（相对旧稀疏版显著加厚 `apps/web`、change request 全路径、snapshot/providers、`packages/{manifest-schema,starter,registry,executor-sdk}`；主张均可回溯 `文件:行号`）
> **读者设定**：有一点编程基础的初学者。每章尽量遵循「比喻 → 真实源码 → 关键路径解释 → 设计取舍」。
> **说明**：本文与同目录下《Claude Code / opencode / hermes-agent / Raven / CodeWhale / nanobot / goose / OpenManus 源码分析》采用同一套 **12 章**结构，方便横向对比。所有 `文件:行号` 相对 `参考项目/suna/`。本工作区为**完整树**（含 `apps/web`、`desktop-electron`、`mobile`、全部 `packages/*`）。找不到 / 不确定的功能会明确写「未找到」，绝不编造。
> **产品轴心**：Kortix 对标「公司操作系统 / 通用任务 Agent 平台」（调研、交付物、自动化、多渠道开工），不是 Claude Code 那种「仓库内写代码优先」的单机编程 Agent。它把 **OpenCode** 当作沙箱里的大脑，自己负责隔离、权限、连接器、计费与 change request。

---

## 第 1 章 项目概览：它是什么，谁在做

### 一句话版本

**Kortix（GitHub 仓 `kortix-ai/suna`）是一个把「公司」字面意义上做成 Git 仓库的 AI Command Center**：配置、Agent、技能、记忆、沙箱规格都在 repo 里；每次会话起一台隔离沙箱，里面跑 OpenCode；只有通过 **change request** 审过的改动才能合回 `main`。许可证是 **Elastic License 2.0**（`LICENSE:1`），不是 MIT——明确限制把本软件作为托管服务提供给第三方（`LICENSE:18-20`）。

README 标题即产品名（`README.md` 开篇 `# Kortix`）；站点元数据把定位钉成「open-source AI command center for your company」（`apps/web/src/lib/site-metadata.ts:16`）。MANIFESTO 第一句更狠（`MANIFESTO.md:3`）：

> A company is going to be a git repository.

### 用比喻理解定位

如果说 Claude Code 是「精装寿司店」（菜单围绕写代码），OpenManus 是「路边摊版调研旅行社」（单机 ReAct + 浏览器），那 Kortix 更像一座**带门禁的科技园 + 自助工位系统**：

- **公司大楼的蓝图**就是一个 Git repo（`kortix.yaml` + `.kortix/opencode/`）；
- **每次开工**给你一间独立工位（云沙箱），工位里已经装好工具链和 OpenCode；
- **干完活想留档**，只能开 CR（change request）让人审批，不能直接在共享大厅墙上涂鸦；
- **前台**可以是 Web / CLI / Desktop / Mobile / Slack / Teams / Email——话都进同一套会话生命周期。

MANIFESTO 把另一半赌注说白（`MANIFESTO.md:7-9`）：AGI 时代需要一个「WordPress / command center」——上下文、Agent、技能、触发器、连接器、记忆落在同一处，而不是四十个工具胶带粘起来。

### 谁在做 / 叫什么名

| 项 | 证据 |
|---|---|
| 组织 / 仓 | `kortix-ai/suna`（README / 仓库 URL） |
| 产品名 | **Kortix**（README 标题、CLI `kortix`、包名 `@kortix/*`） |
| 历史名 Suna | 仓名、`suna-migration/`、若干注释仍保留 |
| License | Elastic 2.0（`LICENSE:1-20`） |
| 版本 | `VERSION` → `0.10.14`；README badge 仍 `0.9.98`（`README.md:12`） |
| 语言 | 主仓 TypeScript；运行时 Bun（API / daemon）；pnpm workspace（`package.json:60`；`pnpm-workspace.yaml:1-3`） |
| Web 包名 | `Kortix-Computer-Frontend`，Next `15.5.21`（`apps/web/package.json:2,159`） |
| Desktop | Electron 壳包 `@kortix/desktop-electron`，包裹 Web（`apps/desktop-electron/package.json:2-7`） |
| Mobile | Expo 应用 `kortix`（`apps/mobile/package.json:2-4`） |

### 技术栈速览

| 层 | 技术 | 位置 / 证据 |
|---|---|---|
| 前端宿主 | Next.js 15 App Router（Turbopack 开发） | `apps/web/package.json:6,159`；`AGENTS.md:179` |
| 客户端 SDK | `@kortix/sdk`（SSoT，禁止宿主直连 API/OpenCode） | `AGENTS.md:91-137`；`packages/sdk/src/index.ts:1-39` |
| API | Bun + Hono OpenAPI，默认 `:8008/v1` | `AGENTS.md:180`；`apps/api/src/index.ts` |
| 数据 | Supabase + Drizzle（`@kortix/db`） | `AGENTS.md:181`；`packages/db/` |
| 清单契约 | `@kortix/manifest-schema`（`kortix.yaml` 校验 SSoT） | `packages/manifest-schema/package.json:2-4` |
| 脚手架 | `@kortix/starter`（init / create-repo 模板） | `packages/starter/package.json:2-4` |
| 市场引擎 | `@kortix/registry`（shadcn 兼容 registry） | `packages/registry/package.json:2-4` |
| 连接器客户端 | `@kortix/executor-sdk`（服务端代发） | `packages/executor-sdk/package.json:2-5` |
| 沙箱 Provider | Daytona / Platinum / E2B / local-docker | `provider-parity.test.ts:11-34` |
| 沙箱内大脑 | OpenCode（`opencode serve`）由 `kortix-agent` 守护 | `apps/sandbox/README.md:15-19`；`kortix-sandbox-agent-server/README.md:6-12` |
| 模型网关 | `apps/llm-gateway` + API 侧 routing | `apps/api/src/llm-gateway/`；`index.ts:740-741` |
| 连接器执行 | `/v1/executor`，凭证留在服务端 | `apps/api/src/executor/execute.ts:1-5` |

```mermaid
flowchart LR
    subgraph Hosts["宿主面"]
      WEB["apps/web Next.js"]
      DESK["desktop-electron"]
      MOB["mobile Expo"]
      CLI["kortix CLI"]
      CH["Slack / Teams / Email / …"]
    end
    SDK["@kortix/sdk"]
    API["apps/api :8008"]
    DB[(Supabase / @kortix/db)]
    SBX["云沙箱<br/>session_id == sandbox_id"]
    DAEMON["kortix-agent"]
    OC["opencode serve"]
    CR["change_requests"]

    WEB --> SDK
    DESK --> WEB
    MOB --> SDK
    CLI --> API
    CH --> API
    SDK --> API
    API --> DB
    API -->|"/v1/p/:id/8000"| SBX
    SBX --> DAEMON --> OC
    OC -->|"commit + kortix cr"| CR
    CR --> API
```

### 与本系列其它项目的轴心差异（先立靶）

- **vs Claude Code / opencode 单机**：Kortix 把 OpenCode **嵌进多租户沙箱 OS**，自己管身份、计费、连接器、CR。
- **vs OpenManus**：OpenManus 是进程内 ReAct 继承链；Kortix 的「想一步做一步」在 **OpenCode 二进制**里，本仓写的是平台脊骨。详见章末与第 12 章对照。
- **vs nanobot / Raven**：nanobot 是个人 IM 管家 + 小核心；Kortix 是企业 command center + Elastic 许可 + 沙箱隔离。

### 与 OpenManus 对照（短注）

同轴心（通用任务 / 调研 / 交付物），但赌注不同：OpenManus 赌「三小时能看懂的 ReAct 继承链」；Kortix 赌「公司仓库 + 一会话一沙箱 + change request」。许可上 OpenManus 是 MIT，Kortix 是 Elastic License 2.0。细节见同目录《OpenManus-vs-Suna-横向对比》。

---

## 第 2 章 全景架构：一张分层地图

### 比喻：科技园的六层楼

1. **门禁与接待（Hosts + SDK）**：Web/CLI/IM/Mobile 只通过 `@kortix/sdk` 或官方 CLI 进园（`AGENTS.md:110-118`）。
2. **调度中心（apps/api）**：会话生命周期、项目 Git、计费、IAM、渠道 webhook、沙箱代理。
3. **工具经纪人（executor + llm-gateway）**：第三方 API 与模型流量在服务端代发，沙箱只持有 scoped token。
4. **工位（sandbox + kortix-agent）**：克隆 repo、切会话分支、拉起 OpenCode、暴露 8000/健康检查/静态预览。
5. **档案室（git + change_requests + DB 镜像）**：真相在沙箱与 git；DB 做列表/同步加速（`MANIFESTO.md:50`）。
6. **契约层（packages）**：manifest-schema / starter / registry / sdk / db / executor-sdk —— 让 CLI、API、Web 共享同一套形状。

### 目录地图（完整检出）

| 路径 | 职责 |
|---|---|
| `apps/web/` | Next.js Command Center UI；thin consumer of SDK |
| `apps/api/` | 多租户控制面；`src/index.ts` 挂载几乎所有 `/v1/*` |
| `apps/cli/` | `kortix init/ship/sessions/cr/…` |
| `apps/sandbox/` | 沙箱 Docker 参考镜像与 entrypoint |
| `apps/kortix-sandbox-agent-server/` | 沙箱内守护进程源码（编译进镜像） |
| `apps/llm-gateway/` | 网关服务实现（API 内亦有 routing 接入） |
| `apps/desktop-electron/` | Electron 壳，加载 Web URL |
| `apps/mobile/` | Expo 移动端 |
| `packages/sdk/` | 宿主唯一数据层 |
| `packages/db/` | Drizzle schema（含 `change_requests`、`session_sandboxes`） |
| `packages/shared/` | 跨包常量/工具 |
| `packages/manifest-schema/` | `kortix.yaml` 规范与校验器 |
| `packages/starter/` | 项目模板（init / create-repo） |
| `packages/registry/` | Marketplace 发现/构建原语 |
| `packages/executor-sdk/` | 连接器调用客户端 |
| `packages/agent-tunnel/` | 设备隧道 |
| `packages/api-contract/` | Zod wire 契约 |
| `packages/llm-catalog/` | 模型目录 |

### Web 侧「Command Center」长什么样

产品文案反复叫 command center（`apps/web/src/lib/site-metadata.ts:16`；营销 hero `apps/web/src/features/marketing/hero.tsx:46`）。应用内真正的操作面是 App Router 下的项目工作区：

| 路由 | 文件 | 作用 |
|---|---|---|
| `/projects/[id]` | `apps/web/src/app/(app)/projects/[id]/page.tsx` | 项目首页：`ProjectHome` 作曲器开新会话 |
| `/projects/[id]/sessions/[sessionId]` | `…/sessions/[sessionId]/page.tsx` | **挂 `useSession` 的会话页** |
| `/projects/[id]/customize/…` | customize pages | Agents / Secrets / Sandbox / Review 等 |
| `/review` | `apps/web/src/app/(app)/review/page.tsx` | Review Center **原型**（mock，无 API） |
| 项目内 Review | `features/workspace/…/review-view.tsx` | 接 `ReviewCenterConnected` 的**真数据** |

`features/` 按域切：`session/`（聊天与工具渲染）、`workspace/`（侧栏/首页）、`review-center/`、`project-files/`、`marketplace/`、`providers/`（Auth / LLM provider UI）。

### API 路由脊骨（控制面一张表）

`apps/api/src/index.ts` 把子系统挂到统一 Hono app（节选，`index.ts:693-851`）：

- `/v1/accounts`、`/v1/auth`、`/scim/v2` —— 身份与企业目录
- `/v1/router`、`/v1/generation`、`/v1/usage` —— 模型路由与用量
- `/v1/billing` —— Stripe 等计费
- `/v1/platform` —— 沙箱平台能力
- `/v1/projects` —— Git 后端项目与会话（含 change-requests）
- `/v1/marketplace` —— Agent/技能市场
- `/v1/git` —— git-proxy（条件挂载）
- `/v1/executor` —— 连接器执行
- `/v1/webhooks/*` —— Slack/Teams/Telegram/Email/Meet/sandbox 生命周期
- `/v1/tunnel` —— 设备隧道
- `/v1/p` —— **沙箱预览/代理**（OpenCode SSE/HTTP 的对外入口）

进程级崩溃守卫刻意「只记日志不拖垮整机」（`index.ts:98-119`）——多租户长驻服务的典型取舍。

### 两个「主循环」必须先分清（钥匙）

| 循环 | 住在哪 | 做什么 |
|---|---|---|
| **平台会话循环** | `apps/api/.../session-lifecycle/` | create → provision sandbox → start/ready → continue/deliver prompt → stop/restart；队列、幂等、背压 |
| **模型-工具循环** | OpenCode（沙箱内二进制） | 经典 Agent：读上下文 → 调工具 → 观察 → 再想；本仓**不重写**这条链 |

守护进程自己声明边界（`kortix-sandbox-agent-server/README.md:6-24`）：只负责监督 `opencode serve`、反代 HTTP/SSE、健康检查与静态预览；triggers/channels/connectors/secrets **不是** daemon 的事。

### SDK 是宿主唯一数据层（架构铁律）

`AGENTS.md:91-137` 写死：

- 逻辑进 SDK，宿主禁止 raw `fetch` / `@opencode-ai/sdk`；
- 一宿主一 client：`createKortix` / `configureKortix`；
- **一整会话一个 hook**：`useSession(projectId, sessionId)`；
- `apps/web` 的 `stores/server-store`、`hooks/opencode/use-*` 是 **shim**（例如 `apps/web/src/stores/server-store.ts:2`：`export * from '@kortix/sdk/server-store'`；`hooks/opencode/use-session-sync.ts:2`：`export * from '@kortix/sdk/react'`）。

Web 唯一接线口：`apps/web/src/lib/kortix-config.ts:3-30` 用 `configureKortix({ backendUrl, getToken, … })` 注入 Supabase JWT 与 toast/通知 sink。

### 四个常被忽略的 packages（摘要）

**`@kortix/manifest-schema`**（`packages/manifest-schema/src/index.ts:1-15`）：`kortix.yaml` 的 canonical schema + 纯函数校验器。消费方：CLI `ship` 预检、后端 CR-merge 闸门、`kortix validate`。无 I/O、无 DB。

**`@kortix/starter`**（`packages/starter/src/index.ts:1-48`）：文件夹模板 + `getStarterFiles()`；默认用户模板 `general-knowledge-worker`，内部还留 `minimal` 给 clone/seed。API create-repo 与 `kortix init` 共用。

**`@kortix/registry`**（`packages/registry/src/index.ts:1-12`）：shadcn 兼容 registry 发现/构建；**安装已改为 agent import**（读源、合并文件、落 CR），不再有确定性 install engine。

**`@kortix/executor-sdk`**（`packages/executor-sdk/src/index.ts:41-50`）：带 token 的 typed client；可走 `/executor/projects/:projectId/...`，让「笔记本上的 CLI」与「沙箱内会话 token」同构调用，秘密仍在服务端。

### 设计取舍（架构层）

- **厚平台、薄宿主**：逻辑进 SDK/API，Web 是 thin consumer（`AGENTS.md:91-133`）。代价是 monorepo 巨大、新人路径长。
- **真相在沙箱 + git，DB 是镜像**：列表快，但调试要同时看三处状态（`MANIFESTO.md:50`）。
- **Elastic 许可**：可自托管，但不能轻松做成「别人的 SaaS 竞品」——商业护城河写进 license。

> 🧠 **一句话**：厚平台、薄宿主——逻辑全压进 SDK 和 API，Web 只是个消费者；真相在沙箱和 git 里，数据库只是加速用的镜像。

---

## 第 3 章 启动流程：从命令到界面

### 比喻：办入园证 → 分配工位 → 开机 → 开门接待

### 3.1 本地 / 云端两条「从零到能聊」

**脚手架（笔记本上建公司仓库）**

`kortix init` 创建带 OpenCode 运行时与 `kortix.yaml` 的工程（`apps/cli/src/commands/init.ts`；模板来自 `@kortix/starter`，`packages/starter/src/index.ts:1-14`）。可把 `.kortix/opencode` 软链到 Claude/Codex/Cursor 原生目录，让同一套 agents/skills 被多种编码 Agent 共享。

**上线**

`kortix ship`：校验 manifest → commit → 补 secrets → push → 连 connector（`apps/cli/src/commands/ship.ts` 帮助文本；manifest 校验走 `@kortix/manifest-schema`，`packages/manifest-schema/src/index.ts:5-11`）。产品承诺「同一份 repo，笔记本与云端行为一致」（`MANIFESTO.md:38`）。

**本地全栈开发**

`pnpm dev` 起 Web(:3000) + API(:8008) + 本地 Supabase + cloudflared 隧道（`AGENTS.md:178-193`；根 `package.json:5`）。secrets 用 dotenvx 密文入库（`AGENTS.md:195-202`）。

### 3.2 一次 Session 在控制面如何「出生」

核心状态机在 `apps/api/src/projects/session-lifecycle/engine.ts`：

1. **`createSession`**（`engine.ts:44`）  
   - 可按 `queuePolicy` 做背压排队（`always` / `on_backpressure` / `never`）。  
   - 支持幂等键；冲突时检查 connector bindings / secrets allowlist；跨租户撞键直接拒绝。  
   - 真正建会话走 `executeCreateSession` → `createProjectSession` / 沙箱 provision。

2. **沙箱 provision**（`apps/api/src/platform/services/session-sandbox.ts:1-12`）  
   - 行插入 `kortix.session_sandboxes`，**主键就是调用方给的 UUID，且等于 project session id**。  
   - Fire-and-forget：先返回 `provisioning`，provider `create()` 在后台跑。  
   - 铸造 executor token 时注释写死：`session_id == sandbox_id`，便于 LLM gateway 把 usage 记到该会话（`session-sandbox.ts:147-149`）。  
   - 路由层不变量：`session_id == sandbox_id == git branch name`（`apps/api/src/projects/routes/r7.ts:348`）。

3. **`startSession`**（`engine.ts:206`）  
   - 调 `openSession`；可选 `waitMs` 服务端 long-poll，直到 `ready` 或截止（避免客户端空转）。

4. **宿主侧（Web 已检出）**  
   - SDK：`configureKortix` / `createKortix` → `useSession`（`packages/sdk/src/react/use-session.ts:3-19`）。  
   - Web 会话页：`ProjectSessionPage` 一次性挂上 `useSession(projectId, sessionId, { enabled, replayStartStash:false, chatEngine:false })`（`apps/web/src/app/(app)/projects/[id]/sessions/[sessionId]/page.tsx:46-100`）。

### 3.3 Snapshot builder：工位镜像怎么来的（加深）

`apps/api/src/snapshots/builder.ts:1-14` 把 builder 定位为「薄编排器」：

1. `(project, slug)` → `ResolvedTemplate`；  
2. 算 content-addressed snapshot 名；  
3. **问 provider**：若镜像已 active 则直接用，否则 inline build。  

关键哲学（同文件注释）：**boot 路径从不信任 DB 行判断「镜像是否存在」——每次问 provider**；DB 的 `project_snapshot_builds` 只是缓存 + 审计 + UI「Fix with agent」（`builder.ts:9-13`）。

触发源枚举（`builder.ts:56-62`）：`session-start` / `project-create` / `cr-merge` / `manual` / `background` / `startup`——说明 **CR 合并也会驱动 rebuild**，公司配置变更会反映到下一台沙箱。

跨副本去重：`waitForProviderBuild` 在 provider 已有同名 build 时轮询结算，超时保持 `building` 失败关闭，避免双份同名构建（`builder.ts:67-72`）。

Provider 列表由 `KNOWN_PROVIDERS` 单一真相约束；parity 测试要求 `daytona/platinum/e2b/local-docker` 与模板 provider 集一致（`apps/api/src/platform/providers/provider-parity.test.ts:11-34`）。`local-docker` 不需要云厂商 API key 也能准入（同文件 `:70-74`）。

沙箱镜像本身（`apps/sandbox/README.md:15-28`）只烘焙 git、opencode、`kortix-agent`；triggers/channels/connectors/secrets **不进镜像**，靠 create-time env / HTTP。

### 3.4 沙箱内开机顺序（daemon）

`kortix-sandbox-agent-server` README 开机流（`README.md:6-24` + Boot flow 段）：

1. 监督进程拉起 `opencode serve`，Hono 反代到 `KORTIX_SERVICE_PORT`（默认 8000）；  
2. `/kortix/health`、`/kortix/refresh`；  
3. 静态预览口 `KORTIX_STATIC_PORT`（默认 3211）——`apps/web` 用 `/proxy/3211/*` 与 `p3211-<sandboxId>` 子域拼预览 URL（daemon README:14-18）；  
4. 先 materialize repo 再 resolve OpenCode config（源码注释强调顺序）。  

### 3.5 Web：从项目首页到会话页的「开门」

1. `/projects/[id]` 渲染 `ProjectHome`（欢迎墙 + `ComposerChatInput`）（`project-home.tsx:46-58`；`page.tsx:6-9`）。  
2. 用户发送 → `writeStartStash` 把首句塞进 SDK start-stash（`page.tsx:17`；`use-session.ts` 头注释）→ `useNewProjectSession` 创建会话并导航。  
3. 到达 `/sessions/[sessionId]`：`useSession` 负责 POST `/start`、sandbox switch、SSE、就绪种子与 canonical OpenCode id（`page.tsx:46-59,87-100`）。  
4. 新鲜会话用 `InstantSessionShell` 交叉淡入；resume 用 `SessionStartingLoader`（`page.tsx:362-410`）。  
5. `ActiveSessionChat` 再挂 `SessionLayout` + `SessionChat`（`page.tsx:649-659`）；聊天消息真相来自 `useSessionSync`（shim → SDK）（`session-chat.tsx:3482-3491`）。

### 设计取舍

- **异步 provision + long-poll start**：首包可到数分钟；用队列与 waitMs 消化。  
- **Snapshot 以 provider 为真相**：避免 DB 漂移；代价是每次 boot 多一次远端查询。  
- **页面拆 `useSession` 生命周期与 `SessionChat` 消息引擎**：`chatEngine:false` 避免双挂 `useSessionSync`（`page.tsx:91-95`）。

---

## 第 4 章 输入捕获与分流：一句话的旅程

### 比喻：多门店收单，后厨只认一种「工单」

### 4.1 入口矩阵

| 入口 | 进园方式 | 关键路径 |
|---|---|---|
| Web | `ProjectHome` / `SessionChat` → SDK | `project-home.tsx`；`session-chat.tsx`；`use-session.ts` |
| Desktop | Electron 加载 Web URL | `apps/desktop-electron/package.json:12-14` |
| Mobile | Expo + SDK | `apps/mobile/package.json` |
| CLI | `kortix sessions` / `sessions-chat` | `apps/cli/src/commands/sessions*.ts` |
| Slack | webhook → binding → lifecycle | `index.ts:788-792`；`channels/slack/` |
| Teams / Telegram / Email / Meet | 各自 webhook | `index.ts:790-796` |
| Triggers | `projectWebhooksApp` + scheduler | `index.ts:775` |

渠道消息最终要变成：**某个 project 上的某个 session + 一段 text（或首次 create+prompt）**。

### 4.2 Web 作曲器路径（加深）

项目首页 `onSend`：计费门 → `writeStartStash(sessionId, { prompt })` → 创建会话 → `router` 进会话页。会话页注释写明：`replayStartStash:false`，因为 Web 有自己的 pending-prompt 交接（`page.tsx:91-92`）；`ActiveSessionChat` 在 canonical OpenCode id 解析后 `migrateStash(routeId → chatSessionId)`（`page.tsx:521-537`）。

`InstantSessionShell` 也读写 start-stash（`instant-session-shell.tsx` import `readStartStash, writeStartStash`），保证「沙箱还在醒、用户已经能打字」的即时感。

### 4.3 续聊投递：`continueSession`

当会话已存在，渠道或调度器调用 `continueSession`（`engine.ts:252`）：

1. 读 `projectSessions`；`failed` 直接放弃；若 `metadata.deletedAt` 存在则视为已删（即使用户状态是 `stopped`）——防止 Slack 回复「复活」已删会话。  
2. `stopped/completed` 会翻回 `running`。  
3. 轮询 `openSession` 直到 `ready`，总预算 `READY_DEADLINE_MS = 300_000`（5 分钟）（`engine.ts:39,320`）。  
4. `deliverWithRetry`：45s 窗口内反复投递，愈合「刚醒的沙箱 404/5xx」（`deliver.ts:12-13,28`）。

**真正把话塞进大脑**的是 `postPrompt`（`engine.ts:668-685`）：

```text
forwardToSandbox(externalId, port=8000, …)
  POST /session/{opencodeSessionId}/prompt_async?directory=/workspace
  body: { parts: [{ type: 'text', text }] }
```

控制面**不解析** tool_calls；它只把用户文本异步丢给 OpenCode。

### 4.4 SDK / 缓存键分流

`useSession` 注释自称「宿主打开会话并流式聊天只需要这一个 hook」（`use-session.ts:3-19`）。缓存键刻意带上 `activeServerKey()`（当前 sandbox id），因为 **每个 project session 是独立沙箱**（`session_id == sandbox_id`），否则切会话会串数据（`packages/sdk/src/react/use-opencode-sessions/shared.ts:17-30`）。

### 4.5 Slack 线程粘性（短）

`bindChatThread` 把 `(platform, workspaceId, threadId) → sessionId` 写入 `chat_threads`——create 后与 webhook 续聊共用同一登记，否则 follow-up 找不到家。

### 设计取舍

- **投递与 ack 解耦**：Slack 先 ack webhook，再后台 deliver——必须有 heal/retry（`deliver.ts:7-11`）。  
- **删除是软删除语义**：靠 `metadata.deletedAt`，不能只看 status 枚举。  
- **Web 即时壳 vs SDK 默认 replay**：宿主可关闭 `replayStartStash`，用自有 UX 交接首句。

---

## 第 5 章 上下文组装：给模型打包

### 比喻：员工手册（repo）+ 本次工单（session）+ 门禁卡权限（grant）

Kortix 的上下文**不是**像 nanobot 那样在 Python 里拼一个 `messages[]` 大数组；而是：

1. **Repo 里的静态智能**：agents / skills / opencode.jsonc  
2. **服务端编译的治理层**：connectors / secrets allowlist / enable flags  
3. **注入进沙箱的环境与 OPENCODE_CONFIG_CONTENT**  
4. **OpenCode 自己维护的会话 transcript**（在沙箱内）  
5. **清单契约**：`@kortix/manifest-schema` 保证 `kortix.yaml` 形状合法

### 5.1 一关心一处：行为 vs 治理

`compile-agent-config.ts` 长注释写清架构转向：

- **行为**（mode/model/temperature/steps/permission/prompt…）住在 `.kortix/opencode/agents/<name>.md`——原样就是合法 OpenCode agent 文件。  
- **治理**（connectors/secrets/skills/kortix_cli/workspace/enabled）住在 `kortix.yaml` 的 `agents:` map。  
- 编译器纯函数：把 md 解析结果拷到 OpenCode `agent` map；`enabled: false` 强制 `disable`；skills 折进 `permission.skill`。

### 5.2 Manifest 校验如何卡死坏配置

`@kortix/manifest-schema` 头注释列出三道关（`packages/manifest-schema/src/index.ts:5-11`）：

1. `kortix ship` 预检（坏清单不 push）；  
2. 后端 CR-merge gate（绕过 CLI 的 raw git / web 编辑也拦得住）；  
3. `kortix validate` 显式报告。  

错误结构化（path + severity + message + 可选行列），调用方可着色诊断。这与 OpenManus「配置错了运行时才炸」形成对照——**Kortix 把配置正确性抬成产品门闩**。

### 5.3 Daemon 如何把「打包结果」塞给 OpenCode

`buildOpencodeConfigContent` 合并多股力量（Executor MCP、Kortix LLM gateway provider、Slack permission 覆盖、`KORTIX_COMPILED_AGENT_CONFIG`）。若皆空则完全使用 repo 内 OpenCode 配置。Daemon **只层叠、不自行编造行为**。

### 5.4 运行时环境字典（出生证明）

`buildSessionRuntimeEnv` 写出控制面与 daemon 的契约键（见旧版加深附录，现织入）：

| Env | 含义 |
|---|---|
| `KORTIX_REPO_URL` / `KORTIX_BASE_REF` | 克隆哪份公司、从哪条基线长出 |
| `KORTIX_BRANCH_NAME` | **等于 `sessionId`**——会话分支名与会话身份合一 |
| `KORTIX_PROJECT_ID` / `KORTIX_SESSION_ID` | 回调 API 时的坐标 |
| `KORTIX_SERVICE_PORT=8000` | daemon 对外端口，也是 `/v1/p/:id/8000` 的约定 |
| `KORTIX_COMPILED_AGENT_CONFIG` | v2 编译产物 JSON 字符串 |
| `KORTIX_INITIAL_PROMPT` | 可选首句 |

读懂这张表，就读懂了「上下文组装」在平台侧的落点：不是拼 `messages[]`，而是**写 env + 写 git 分支 + 写编译配置**。

### 设计取舍

- **「OpenCode 原生文件即行为源」**降低双份配置漂移；代价是控制面必须学会读 git 里的 md。  
- **治理不下放进 md**：避免 Agent 自己改自己的权限边界时绕过 YAML——CR 合并后才生效。  
- **manifest-schema 三处复用**：一份 schema 服务 CLI/API/未来工具，避免「各写各的 YAML 方言」。

> 🧠 **一句话**：Kortix 的「上下文组装」不是在内存里拼一个消息数组，而是写 git 分支 + 写环境变量 + 写编译好的配置——真相落在文件里，不落在进程里。

---

## 第 6 章 Agent 主循环：项目的心脏

### 先泼冷水：本仓没有 `while True: think(); act()`

OpenManus 的心脏在 `ToolCallAgent.think/act`；nanobot 的心脏在 `AgentRunner`。  
**Kortix 的「模型心脏」在 OpenCode 进程内**；本仓心脏是 **Session Lifecycle + Daemon Supervisor + Proxy +（宿主侧）`useSession`**。把这一点写进标题，是为了防止读者在 `apps/api` 里找 ReAct 空转。

### 6.0 先看一个真实任务：一句 Slack 消息，如何变成一个待审的 CR

抽象的「三套循环」听着绕，先看一个具体任务落地。同事在 Slack 里 @ 机器人：**「把落地页标题改得更有力一点。」** 跟着这句话走一遍，你就知道每套循环各自接了哪一棒：

| 阶段 | 谁在干 | 干了什么 | 源码锚点 |
|---|---|---|---|
| ① 收单 | **平台循环** | Slack webhook 进来，按 `(平台,工作区,线程) → 会话` 找到（或新建）这次对话 | `index.ts:788-792`；`bindChatThread` |
| ② 叫醒工位 | **平台循环** | `continueSession`：会话冬眠了就唤醒沙箱，最多等 5 分钟到「就绪」 | `engine.ts:252`；`READY_DEADLINE_MS` |
| ③ 送话 | **平台循环** | `deliverWithRetry` 在 45 秒窗口里反复投递，愈合「刚醒的沙箱还没缓过来」 | `deliver.ts:28`；`postPrompt`（`engine.ts:668-685`） |
| ④ 真正思考 | **OpenCode 循环** | 沙箱里的 OpenCode 读 repo、想一步、改落地页文件、再想——这才是 ReAct 发生的地方 | OpenCode 进程内（本仓不实现） |
| ⑤ 留档 | **OpenCode 循环** | 改完不直接进主干，而是 `git commit` + `kortix cr open` 开一个 change request | `cr.ts:38-40` |
| ⑥ 回传 | **宿主循环** | 若你正开着 Web 看，`useSession` 通过 SSE 把每一步实时画到屏幕上 | `use-session.ts:3-19` |
| ⑦ 治理 | **平台循环** | CR 出现在 Review Center，人点了 merge 才真正改变公司；合并还会触发沙箱镜像重建 | `r9.ts:19-21`；builder 源 `cr-merge` |

看懂这张表，就抓住了 Kortix 和 OpenManus 最大的不同：**任务的终点不是「模型说完了」，而是「产出一个可以被人审的 CR」。** 平台循环负责把话可靠地送达、把工位叫醒；真正的想与做在 OpenCode 里；而所有改动都必须经过 change request 这道人类闸门，才能落进「公司」这个 Git 仓库。

```mermaid
flowchart LR
    SLACK["💬 Slack：改改落地页标题"] --> PLAT
    subgraph PLAT["① 平台循环（本仓）"]
        FIND["找到会话"] --> WAKE["叫醒沙箱"] --> SEND["可靠送话"]
    end
    SEND --> OC
    subgraph OC["② OpenCode 循环（沙箱内）"]
        THINK["读repo→想→改文件→再想"] --> COMMIT["git commit + kortix cr open"]
    end
    COMMIT --> CR["📋 change request（open）"]
    CR --> REVIEW["🧑‍⚖️ 人在 Review Center 审"]
    REVIEW -->|"merge"| MAIN["公司主干变了<br/>+ 触发镜像重建"]
    OC -.->|"SSE 实时回传"| HOST["🖥️ 宿主循环 useSession"]
    style PLAT fill:#eaf1ff
    style OC fill:#e7f8f1
    style CR fill:#fff3df
    style MAIN fill:#fff3df
```

> 🧠 **一句话**：平台循环负责「把话可靠送到、把工位叫醒」，OpenCode 循环负责「想和做」，宿主循环负责「实时显示」——三棒接力，终点是一个待人审的 CR，不是一句「我做完了」。

### 6.1 分清两套循环（本章核心）

```mermaid
flowchart TB
    subgraph PLATFORM["平台循环（本仓实现）"]
      C["createSession"] --> P["provisionSessionSandbox"]
      P --> S["startSession / openSession"]
      S --> D["continueSession / deliverWithRetry"]
      D --> PP["postPrompt → prompt_async"]
      S --> STOP["stop / restart / queue drain"]
    end
    subgraph OPENCODE["OpenCode 循环（沙箱二进制）"]
      T["读 transcript + tools"] --> M["调 LLM（经 gateway）"]
      M --> TOOL["本地工具 / Executor / CLI"]
      TOOL --> T
    end
    subgraph HOST["宿主循环（SDK + Web）"]
      US["useSession: /start + SSE + sync"] --> UI["SessionChat 渲染 turns"]
    end
    PP --> OPENCODE
    US --> S
    OPENCODE -->|SSE via /v1/p| US
```

| 问题 | 平台循环回答 | OpenCode 循环回答 |
|---|---|---|
| 这台工位有没有就绪？ | `stage==='ready'`（server-truth） | — |
| 这句话送进去了吗？ | `deliverWithRetry` / `postPrompt` | 接收 `prompt_async` |
| 下一步调哪个工具？ | **不负责** | 模型 + tool schema |
| 消息列表从哪来？ | DB 镜像可同步 | 沙箱内 transcript 为真相 |
| 宿主如何订阅？ | — | SSE；`useSession` / `useSessionSync` |

### 6.2 平台心脏：会话生命周期状态机

```mermaid
stateDiagram-v2
    [*] --> Creating: createSession
    Creating --> Queued: backpressure
    Queued --> Creating: drain queue
    Creating --> Provisioning: insert session_sandboxes
    Provisioning --> Ready: openSession stage=ready
    Provisioning --> Failed: provider/boot error
    Ready --> Running: prompt_async / user chat
    Running --> Stopped: stop/hibernate
    Stopped --> Running: continueSession wake
    Running --> Failed: terminal error
    Ready --> Deleted: deleteSession metadata.deletedAt
```

关键代码锚点：

- 创建与幂等：`engine.ts:44`  
- 启动与 long-poll：`engine.ts:206`  
- 续聊与 5 分钟 ready 等待：`engine.ts:252,39,320`  
- 投递愈合：`deliver.ts:28`  
- `postPrompt`：`engine.ts:668-685`  

`openSession`（`projects/routes/shared.ts`）负责把 DB 行 + provider 状态解析成 `stage`，并可在 stopped 时 resume / 无盒时 provision——continue 等待的不只是「读状态」，可能正在**叫醒冬眠盘**。

### 6.3 沙箱心脏：监督 OpenCode，而不是取代它

Daemon 职责清单（README:6-12）：

1. `spawn` / 崩溃重启 / 信号排空 `opencode serve`  
2. 反代其 HTTP + SSE 到端口 8000  
3. `/kortix/health`、`/kortix/refresh`  
4. 静态预览端口 3211  

就绪后降低 liveness 频率（避免空闲沙箱吃满 CPU）是典型平台味优化。

### 6.4 宿主心脏：`useSession` 在 Web 上的挂载契约

SDK 头注释（`use-session.ts:3-19`）：

> Everything sandbox-shaped is internal… The host imports `createKortix` + `useSession` and NOTHING else runtime-related.

Web 会话页忠实执行（`page.tsx:46-100`）：

- **一次**挂载 `useSession(projectId, sessionId)`；  
- 计费门挡住无 plan 账户，避免永远轮询不会 provision 的沙箱；  
- `chatEngine:false`：页面只吃 boot/lifecycle；`SessionChat` 自己挂 `useSessionSync`；  
- 中段重连才用 `useSandboxConnection`，初始就绪相信 server `stage`（`page.tsx:55-58,419-424`）。

这解释了为何第 4/6 章要同时谈「平台循环」与「宿主循环」：没有 `useSession`，Web 仍会手搓 7 步 mount——页头注释说他们已经删掉那套（`page.tsx:49-51`）。

### 6.5 一次用户回合的端到端时序

```mermaid
sequenceDiagram
    participant U as User/Web/Slack/CLI
    participant SDK as @kortix/sdk
    participant API as apps/api
    participant P as /v1/p proxy
    participant D as kortix-agent
    participant O as opencode
    participant X as /v1/executor
    participant G as LLM gateway

    U->>SDK: useSession / send
    SDK->>API: /start + stream
    API->>API: continueSession / startSession
    API->>P: forwardToSandbox prompt_async
    P->>D: POST /session/:id/prompt_async
    D->>O: reverse proxy
    O->>G: chat.completions
    O->>X: connector call (scoped token)
    X-->>O: tool result
    O-->>D: SSE events
    D-->>P: SSE
    P-->>SDK: stream
    SDK-->>U: SessionChat turns
    Note over O: 之后 agent 可 git commit + kortix cr open
```

### 6.6 故障与自愈

- **投递层**：45s `deliverWithRetry`（`deliver.ts`）。  
- **Web 层**：hibernated 但 resumable 的沙箱会自动 invalidate `/start` 最多 3 次（`page.tsx:106-139`）。  
- **Daemon 层**：可恢复错误时可 auto-resume turn（源码 `turn-auto-resume.ts`；子 agent 失败留给父模型）。  
- **控制面**：dead-letter 标记 session failed。

### 6.7 和「主循环」相关的明确边界

| 问题 | 答案 |
|---|---|
| tool_calls 解析在哪？ | OpenCode 内（本仓未见等价实现） |
| messages 压缩在哪？ | 平台级 Curator **未找到**；OpenCode 可有 compress 工具（SDK 工具标签） |
| 最大步数？ | Agent md frontmatter 的 `steps` 等可编译进配置，执行仍由 OpenCode 解释 |
| Web 是否实现 ReAct？ | **否**；只渲染 OpenCode 事件与 turns |

### 设计取舍

- **复用 OpenCode = 站在巨人肩上**：立刻继承工具生态；代价是调试跨 API + daemon + OpenCode 三进程。  
- **平台循环做得极重**：幂等、背压、跨租户隔离、删除守卫——SaaS 多租户税，OpenManus 单机脚本不必付。  
- **宿主循环收成一 hook**：换 mobile/whitelabel 时复用同一套就绪语义。

> 🔍 **源码指路**：`session-lifecycle/engine.ts:44`（createSession）｜`:206`（startSession long-poll）｜`:252`（continueSession）｜`:668-685`（postPrompt → prompt_async）｜`deliver.ts:28`（投递愈合）｜`kortix-sandbox-agent-server/README.md:6-24`（daemon 监督 OpenCode）｜`packages/sdk/.../use-session.ts:3-19`（宿主唯一 hook）

### 与 OpenManus 对照（短注）

OpenManus：`BaseAgent.run` → `think/act` 同进程。Kortix：平台只保证「工位就绪 + 把话送进去」；思考与工具在另一进程。选型时不要拿 `engine.ts` 去对 `ToolCallAgent.py`。

---

## 第 7 章 工具系统与权限：模型的「手」和「紧箍咒」

### 7.1 三双手

1. **OpenCode 本地工具**：文件、shell、其插件——跑在沙箱 OS 内，天然按会话分支隔离。Web 用 `features/session/tool/tools/*` **渲染**这些工具结果（如 `bash-tool.tsx`、`read-tool.tsx`、`task-tool.tsx`），不在浏览器里执行。  
2. **Kortix Executor（连接器）**：OpenAPI/HTTP/MCP 等，**在 API 进程代发**，凭证在服务端组装（`execute.ts:1-5`）。沙箱只看到 scoped token。客户端可用 `@kortix/executor-sdk`（`packages/executor-sdk/src/index.ts:41-50`）。  
3. **沙箱内 `kortix` CLI**：`cr` / `secrets` / `sessions`… 用 `KORTIX_CLI_TOKEN` 回调（`cr.ts:38-40`）。

### 7.2 Executor：秘密不过境

这是 Kortix 整个安全模型里最关键的一招，值得先用一张图钉住：**沙箱里的 Agent 永远拿不到真凭证。** 它要调第三方（发 Slack、写 Notion）时，只能把请求交给服务端的 Executor；真正的 API key 是在 **API 进程**这一侧才附加上去的，沙箱手里从头到尾只有一个受限的临时 token。

```mermaid
flowchart LR
    subgraph SBX["沙箱（会话工位）· 只有 scoped token"]
        AGENT["OpenCode Agent<br/>「帮我调用 connector X」"]
    end
    subgraph API["apps/api 进程（可信）· 保管真凭证"]
        POLICY{"policy 引擎<br/>这个动作放行吗？"}
        SECRET["取出 resolved secret<br/>附加认证头"]
        FETCH["fetch 第三方"]
    end
    THIRD["🌐 第三方 API"]
    AGENT -->|"binding + args<br/>不含真 key"| POLICY
    POLICY -->|"always_run"| SECRET
    POLICY -->|"require_approval"| WAIT["🧑 等人在 Review Center 点头"]
    POLICY -->|"block"| DENY["拒绝"]
    WAIT -->|"批准"| SECRET
    SECRET --> FETCH --> THIRD
    THIRD -->|"结果"| AGENT
    style SBX fill:#fdeded
    style API fill:#eaf1ff
```

`execute.ts` 开篇即契约：给定 binding + resolved secret + args，**在此处**附加认证头并 `fetch`；sandbox never sees credentials（`execute.ts:1-5`）。API 挂载：`/v1/executor`（`index.ts:772`）。

Policy 引擎（`executor/policy.ts`）按 glob 评 `always_run | require_approval | block`；可与 Review Center 的 executor approval 行联动（见第 10 章）。它的默认档位很聪明——`default_mode = risk` 下，**读操作直接放行、写/危险操作要人点头**（`policy.ts:126-129,161-176`），比「全部放行」安全得多，又不会烦到每次读取都要确认。

> 🧠 **一句话**：Agent 只递请求、不碰钥匙；真凭证锁在 API 进程里。危险动作默认要人点头——这就是「秘密永不过沙箱边界」。

### 7.3 Agent 权限与 Grant

- Markdown frontmatter 的 `permission` 树随 OpenCode 行为配置下发。  
- 账户级 **agent grant**：`mintExecutorToken` 时 `resolveAgentGrant`（`session-sandbox.ts:147-154` 附近）；失败则 fail-safe。  
- IAM：`session-gate.ts` 是**人类登录会话**策略，与「project session 沙箱」同名不同物。

### 7.4 沙箱与网络边界

- Provider：`daytona` / `platinum` / `e2b` / `local-docker`（`provider-parity.test.ts:11-34`）。  
- 预览代理：`/v1/p/:sandboxId/:port/*` 必须鉴权 + rate limit。  
- Snapshot 上传 URL 有 SSRF 防护测试——平台安全意识强于 OpenManus 默认开放浏览器工具。

### 7.5 审批与人工门闩

- Review Center：原生 review items + 适配的 CR + executor approvals（`review-center-connected.tsx:3-14`）。  
- Change request：合入 main 才改变公司真相（第 10 章全路径）。  
- Web 会话内也有 permission / question self-heal（SDK `usePermissionSelfHeal` / `useQuestionSelfHeal`，`session-chat.tsx:151-154`）。

### 设计取舍

- **连接器集中执行**：安全与审计友好；代价是多一跳。  
- **UI 只渲染工具**：Web 的 tool components 是展示层，防止把执行逻辑漏进浏览器。  
- **双 token 模型**（用户 JWT vs 沙箱 CLI/executor token）：心智负担高，但分离「人」与「工位机器人」。

---

## 第 8 章 上下文压缩与记忆

### 诚实清单

| 能力 | 本仓状态 |
|---|---|
| 对话 transcript 存储 | 沙箱 OpenCode + DB 同步镜像（`MANIFESTO.md:50`） |
| 公司长期记忆 | **文件进 repo**（`MANIFESTO.md:67`：「Memory — files for now…」） |
| LLM 自动压缩 / Dream | **未找到** Dream / company-brain 化合物实现（愿景在 MANIFESTO） |
| OpenCode `compress` 工具 | SDK 工具注册表有展示标签；**执行在 OpenCode 内** |
| Web 工具渲染 | `dcp-compress-tool.tsx` / `dcp-distill-tool.tsx` 等为 UI 呈现 |
| prompt cache | **未找到**平台级统一实现 |
| 上下文窗口字段 | 模型目录元数据含 context window——不是压缩器 |

### 记忆如何「化合物」

MANIFESTO 策略：技能与记忆以 Markdown/文件形式活在公司仓库里，随 CR 进化（`MANIFESTO.md:67`）。这和 Raven/EverOS 的向量记忆不同——**Kortix 先押宝 git 可 diff 的文本记忆**。

SDK 提供 `formatTranscript` 把会话导出为 Markdown（`packages/sdk/src/index.ts` transcript 导出），方便人读与归档，但不是自动压缩器。

### 设计取舍

- **记忆 = 版本化文件**：可审、可回滚、可 grep；长会话仍依赖 OpenCode 自身上下文管理。  
- 对「要做常驻私聊管家」的 nanobot 用户：Kortix 偏「项目/公司工作记忆」，不是 IM 个人日记。

---

## 第 9 章 子 agent 与多 agent：分身术

### 9.1 同项目多 Agent（角色分身）

- 多个 `.md` agent + `kortix.yaml` 里的 `agents:` 治理项。  
- `mode: primary | subagent | all` 出现在编译后的配置——**语义由 OpenCode 解释**。  
- Web customize → Agents 视图可编辑（`features/workspace/customize/sections/view/agents-view.tsx`）。  
- SDK `useVisibleAgents` 供会话侧选择可见 Agent。

### 9.2 同配置海量并行（工位分身）

这是 Kortix 相对 OpenManus PlanningFlow 的关键差异：**并行靠隔离沙箱，不靠一个进程里调度多个角色**。  
`MANIFESTO.md:48`：五十个会话互不污染；冲突交给 git merge。  
每个会话独立 `session_id == sandbox_id`（SDK `shared.ts:17`；`r7.ts:348`）。

### 9.3 市场与继承

- `apps/api/src/marketplace/`：目录型分发。  
- `@kortix/registry`：发现/构建；安装走 **agent import + CR**（`packages/registry/src/index.ts:8-11`）。  
- CLI `kortix agents` / `marketplace`。

### 9.4 子 agent 失败归属

Daemon auto-resume 注释倾向：Task/subagent 失败应由**父模型**处理。Web 有 `task-tool.tsx`、`agent-spawn-tool.tsx` 等渲染器，证明嵌套发生在 OpenCode 工具层。

### 设计取舍

- **并行 = 多沙箱**：账单与冷启动成本高，正确性模型简单（git）。  
- **OpenManus 式 PlanningFlow**：本仓**未找到**同构实现；长任务规划更多依赖 Agent prompt / skills / 人类 CR 节奏。

### 与 OpenManus 对照（短注）

OpenManus 用 `PlanningFlow` 在一进程里逐步派发 `[AGENT]`；Kortix 用「多沙箱 + 多 agent md + CR」换隔离与可审计性。

---

## 第 10 章 生态：命令、扩展、Change Request 全路径

### 10.1 CLI 表面（公司遥控器）

`apps/cli/src/commands/` 可见：`init`、`ship`、`sessions`、`sessions-chat`、`cr`、`agents`、`skills`、`connectors`、`secrets`、`triggers`、`marketplace`、`channels`、`executor`、`sandbox(es)`、`login/logout`、`self-host`、`doctor`…——覆盖从脚手架到审批的闭环。

### 10.2 Change Request 完整路径（本章硬货）

#### 数据模型

`packages/db/src/schema/kortix.ts:2719-2755`：

- 状态枚举：`open | merged | closed`（`:2726-2730`）  
- 关键列：`number`、`base_ref`/`head_ref`、`head_commit_sha`、`origin_session_id`、`merge_commit_sha`…  
- 注释：CR 是**元数据**；真正的 fetch/diff/merge 走 `apps/api/src/projects/git.ts`，后端可是 GitHub/GitLab/纯 git（`:2720-2724`）。

序列化与「request changes」笔记落在 `metadata.requested_changes`（`apps/api/src/projects/change-requests.ts:1-14,72-95`）。

#### API 路由

| 方法 | 路径 | 文件 |
|---|---|---|
| GET | `/:projectId/change-requests` | `routes/r8.ts:225-227` |
| POST | `/:projectId/change-requests` | `r8.ts:272-274` |
| GET/PATCH | `/:projectId/change-requests/:crId` | `r8.ts:547-586` |
| POST | `…/request-changes` | `r8.ts:641-643` |
| GET | `…/diff`、`…/merge-preview` | `r8.ts:731-796` |
| POST | `…/merge` | `r9.ts:19-21`（及 r8 尾部） |
| POST | `…/close`、`…/reopen` | `r9.ts:180-229` |

合并成功后可触发 connector reconcile 与 snapshot rebuild 源 `cr-merge`（builder 触发源枚举；`r9.ts` merge 处理）。

#### CLI

`kortix cr`（`apps/cli/src/commands/cr.ts:13-40`）：`ls/show/diff/open/merge/close/reopen`。沙箱内自动读 `KORTIX_CLI_TOKEN` + `KORTIX_PROJECT_ID`（`:38-40`）——Agent 不用登录。

#### Web UI

1. **项目文件侧**：`change-requests-panel.tsx`、`change-request-detail-dialog.tsx`、`open-change-request-dialog.tsx`；hooks `features/project-files/hooks/use-change-requests.ts`。  
2. **侧栏入口**：`project-change-requests-nav.tsx`。  
3. **Review Center（真数据）**：`ReviewCenterConnected`（`review-center-connected.tsx:3-14,88-148`）  
   - CR approve → `useMergeChangeRequest`  
   - reject → `useCloseChangeRequest`  
   - request changes → 持久化笔记 + **投递回 originating agent**（后端可唤醒沙箱）  
   - `cr:` 适配 id 不走通用 `/act`（会 409），必须走源流程（`:107-109`）  
4. **`/review` 路由**：仅 mock 原型（`review/page.tsx:8-18`），**不要**当成生产 API。生产入口在项目 customize 的 Review 视图（`review-view.tsx:4-32`）。  
5. **SDK**：`packages/sdk/src/core/rest/projects-client/change-requests.ts`；`packages/sdk/src/react/use-change-requests.ts`。

#### Agent 侧文档/技能

Starter 模板含 change-requests 参考文档（`packages/starter/templates/base/.kortix/opencode/skills/kortix-system/references/kortix/change-requests.md`）；Web docs：`apps/web/content/docs/work/change-requests.mdx`。

#### 生命周期一句话

```text
沙箱内 commit → kortix cr open
  → DB change_requests(status=open)
  → Web Review / CLI show/diff
  → merge → git merge + status=merged +（可选）snapshot rebuild
  → 或 close / request-changes → 再投递 agent 修订
```

### 10.3 连接器与 MCP / registry / starter

- Pipedream 等目录在 `apps/api/src/executor/`。  
- `@kortix/executor-sdk` 给外部 TS 代码同构调用。  
- `@kortix/registry` + API marketplace：发现与 agent import。  
- `@kortix/starter`：统一脚手架，避免 CLI/API 各写一套模板。

### 10.4 渠道生态

Slack/Teams/Telegram/Email/Meet webhook 进 API（`index.ts:788-796`）。Slack 会话会改 OpenCode permission（避免 `question` 卡死）。

### 10.5 扩展方式（给二次开发者）

1. 在 repo 加 agent `.md` / skill，开 CR。  
2. 在 `kortix.yaml` 挂 connector / trigger / sandbox 规格（先过 manifest-schema）。  
3. 走 marketplace agent import。  
4. 自建连接器经 executor 协议暴露。  
5. 深改：动 `@kortix/sdk`（TDD 与导出契约，`AGENTS.md:99-108`）或 daemon。

### 10.6 迁移与历史包袱

`projects/suna-migration/`：从旧 Suna/AgentPress 世界迁到 OpenCode DB 映射——证明产品经历过一次「运行时换心」。

> 🧠 **一句话**：Change Request 是 Kortix 的产品原语，不是附属功能。Agent 改了什么都不算数，直到一个 CR 被人 merge 进主干——这就是「AI 出力、人类治理」的落地形态。

---

## 第 11 章 功能特性：认证、模型与「思考」

### 11.1 认证

- 人类：Supabase JWT；Web `AuthProvider`（`apps/web/src/features/providers/auth-provider.tsx:29+`）+ `configureKortix({ getToken })`（`kortix-config.ts:30-32`）。  
- 程序：API key / PAT / account token。  
- 沙箱：`KORTIX_TOKEN` / CLI token / executor token 分层。  
- 预览：`POST /v1/p/auth` 种 cookie。  
- 企业：SCIM（`/scim/v2`）、SSO 相关 oauth 路由。  
- 会话门禁：lifetime / idle / revoked（IAM `session-gate`）——**人类会话**。

### 11.2 模型路由与网关

- API `mountLlmGateway`（`index.ts:740-741`）+ `/v1/router`。  
- 项目级 routing policy；Web customize 有 Gateway 视图（`gateway-routing.tsx` 等）+ `useGatewayRoutingPolicy`。  
- Daemon：存在 gateway 时 OpenCode 走合成 `kortix` provider。  
- 计费：`billing/` + compute metering；Web 会话页有 `can_run` / upgrade dialog 门（`page.tsx:66-85`）。

### 11.3 「思考」/ Reasoning

- 网关 wire 可带 `reasoning_effort`；项目策略可 clamp。  
- Web 有 `reasoning-effort-selector.tsx`（接 `useGatewayRoutingPolicy`）。  
- 对外分享会话时 API 可省略 reasoning，标 `reasoning_omitted`。  
- **未找到**：跨模型统一的 thinking blocks 一等剥离服务；渲染主要在 SessionChat turn UI。

### 11.4 其它特性速查

| 特性 | 状态 | 证据 |
|---|---|---|
| 多渠道 | 有 | `index.ts` webhooks |
| Change Request | 有 | DB + r8/r9 + CLI + Web Review |
| Review Center | 有（真数据在项目内；`/review` 为 mock） | `review-center-connected.tsx`；`review/page.tsx` |
| 预览/静态站点 | 有 | daemon 3211；`/v1/p` |
| Tunnel | 有 | `/v1/tunnel` |
| Self-host | 有入口 | CLI `self-host`；`self-host/` |
| Desktop / Mobile | 有 | `apps/desktop-electron`；`apps/mobile` |
| Web Command Center | **已检出** | `apps/web` 全文路径如上 |

---

## 第 12 章 总结：设计哲学与取舍

```mermaid
flowchart TB
    GOAL["目标：可自托管的公司级 AI Command Center<br/>公司 = Git repo"]
    GOAL --> P1["支柱1 会话=隔离沙箱<br/>session_id == sandbox_id == branch<br/>→ 第3/6章"]
    GOAL --> P2["支柱2 大脑外包给 OpenCode<br/>平台只做 lifecycle/proxy/config<br/>宿主只挂 useSession<br/>→ 第5/6章"]
    GOAL --> P3["支柱3 变更必须经 CR<br/>API+CLI+Review Center<br/>→ 第7/10章"]
    P1 & P2 & P3 --> BASE["地基：SDK SSoT + manifest-schema + Executor 守秘<br/>+ LLM Gateway + 多 Provider snapshot<br/>→ 第2/3/7/11章"]
```

### 五个独特想法

**① 公司字面即仓库。** agents/skills/memory/sandbox 规格全部进 repo，可 grep、可 diff（`MANIFESTO.md:27-36`）。

**② `session_id == sandbox_id == git branch`。** 路由注释（`r7.ts:348`）；executor token 铸造注释（`session-sandbox.ts:147-149`）；SDK 缓存键注释（`shared.ts:17`）。

**③ 平台循环 ≠ 模型循环 ≠ 宿主循环。** API 精耕 create/start/continue/deliver；ReAct 留给 OpenCode；Web 用 `useSession` 吃 server-truth 就绪（第 6 章）。

**④ 秘密永不过沙箱边界。** Executor 服务端附凭证；`executor-sdk` 亦然。

**⑤ Change Request 是产品原语。** Schema（`kortix.ts:2719+`）+ API r8/r9 + CLI `cr` + Review Center 适配器——「AI 劳动力 + 人类治理」。

### 主要代价

- **复杂度税**：monorepo + 多 provider + 队列 + 幂等 + SDK 契约，学习曲线远高于 OpenManus。  
- **冷启动税**：snapshot/sandbox 分钟级；靠 warm、long-poll、deliver heal、Web 即时壳弥补。  
- **许可税**：Elastic 2.0 限制托管竞品（`LICENSE:18-20`）。  
- **大脑不可见**：深度 Agent 行为要读 OpenCode 上游；本仓分析无法替代。  

### 横向对比速览（对系列文档）

| 维度 | Suna / Kortix |
|---|---|
| 团队 / 许可 | kortix-ai；**Elastic License 2.0** |
| 技术栈 | TS/Bun monorepo + Hono + Supabase + Next + OpenCode-in-sandbox |
| 架构 | 控制面 session-lifecycle + 沙箱 daemon + OpenCode；SDK SSoT |
| 模型 | LLM Gateway 路由；OpenCode `kortix/` provider |
| 工具量级 | OpenCode 工具 + 服务端 Executor/MCP + 内置 CLI |
| 扩展 | yaml + md agents/skills + registry/marketplace + connectors |
| 主循环 | 平台：`session-lifecycle/engine.ts`；模型：OpenCode；宿主：`useSession` |
| 权限 | IAM + agent grant + executor 守秘 + CR 治理 |
| 压缩记忆 | repo 文件记忆；**未见**平台级 LLM 压缩 |
| 前端 | **完整检出**：Command Center + Session + Review |

**一句话收束**：Claude Code 回答「怎么在这个仓库把代码写好」；OpenManus 回答「怎么用一份可读的 ReAct 链做调研交付」；nanobot 回答「怎么让个人 Agent 长期住在聊天软件里」；而 **Kortix/Suna 回答的是——怎么把一家公司变成可 clone 的 Git 仓库，让成百上千个隔离沙箱里的 OpenCode 劳动力在 change request 纪律下，日夜改进这家公司本身**。

### 建议阅读顺序（完整检出）

1. `MANIFESTO.md` → 世界观  
2. `AGENTS.md` SDK 铁律 + `apps/web/src/lib/kortix-config.ts`  
3. `packages/sdk/src/react/use-session.ts` + Web `sessions/[sessionId]/page.tsx`  
4. `session-lifecycle/engine.ts` 的 create/start/continue/postPrompt  
5. `snapshots/builder.ts` + `session-sandbox.ts` 头注释  
6. `kortix-sandbox-agent-server/README.md`  
7. `change-requests.ts` + `routes/r8.ts`/`r9.ts` + `review-center-connected.tsx`  
8. `packages/manifest-schema` / `starter` / `registry` / `executor-sdk` 各自 `package.json` + `src/index.ts`  

### 命名排雷

| 你看到的名字 | 实际指什么 |
|---|---|
| Suna | 历史产品/仓名；迁移代码在 `suna-migration/` |
| Kortix | 当前产品名、CLI、npm scope `@kortix/*` |
| session（IAM） | 人类登录会话 |
| session（project） | Agent 工位；常与 sandbox 同 id |
| sandbox_id | 多数路径上 **== project session id** |
| opencode_session_id | OpenCode 内部对话 id，**不等于** project session id |
| `/review` vs Review Center | 前者 mock；后者项目内 Connected 真数据 |
| VERSION `0.10.14` vs badge `0.9.98` | 以 `VERSION` 文件为准 |


---

## 加深织入 A：Web Command Center UX 细读（补强第 2/3/4 章）

### A.1 路由与壳层

App Router 下 `(app)` 组是登录后的主壳。项目页 `/projects/[id]` 不直接聊天，而是 `ProjectHome`：欢迎区 + 作曲器 + 快捷入口（Agents / Secrets / Sandbox / Triggers / Marketplace 等图标按钮，见 `project-home.tsx` 后半段）。发送时父页 `ProjectIndexPage`（`page.tsx:24+`）负责：

1. `useProjectCanRun` / `useAccountState` 计费门；  
2. `writeStartStash` 写入首句；  
3. `useNewProjectSession` 创建会话并导航到 `/sessions/[sessionId]`。

会话页注释把职责切得很干净（`sessions/[sessionId]/page.tsx:46-59`）：**SDK 拥有运行时生命周期**；页面保留 billing gate、instant-shell/loader 交叉淡入、fresh-session hand-off、restart/error 卡片。

### A.2 `useSession` 参数语义（宿主必须懂）

```ts
useSession(projectId, sessionId, {
  enabled: !!user && !billingGatePending && !noPlan,
  replayStartStash: false,
  chatEngine: false,
})
```

证据：`page.tsx:96-100`。

| 参数 | 含义 |
|---|---|
| `enabled` | 无用户 / 计费未决 / 无 plan 时不发起 `/start`，避免空转 |
| `replayStartStash` | Web 关闭 SDK 默认回放；改用 `migrateStash` + InstantShell |
| `chatEngine` | false 时不在页面层挂 `useSessionSync`；交给 `SessionChat` |

SDK 内部仍会：long-poll `/start`、写入 connection store 的 readiness、打开 SSE、解析 canonical OpenCode id（`use-session.ts:5-16`）。新鲜会话 404 有 grace retry：`FRESH_START_404_RETRIES = 12` × 800ms（`use-session.ts:66-70,79-88`）。

### A.3 Shim 清单（证明 thin consumer）

| Web 路径 | 实际导出 |
|---|---|
| `apps/web/src/stores/server-store.ts:2` | `@kortix/sdk/server-store` |
| `apps/web/src/hooks/opencode/use-session-sync.ts:2` | `@kortix/sdk/react` |
| `apps/web/src/hooks/opencode/use-canonical-opencode-session.ts:2` | `@kortix/sdk/react` |
| `apps/web/src/hooks/opencode/use-opencode-events.ts` | SDK react re-export |
| `apps/web/src/lib/api-client.ts:1` | `@kortix/sdk/api-client` |
| `apps/web/src/lib/opencode-sdk.ts:1` | `@kortix/sdk/opencode-client` |

`AGENTS.md:128-133` 明确：冲突时 **keep the shim**，新逻辑 port 进 SDK。

### A.4 Session 功能目录地图（`features/session/`）

完整检出后可见该目录极大（200+ 文件量级）。职责分层：

- **壳**：`session-layout.tsx`、`instant-session-shell.tsx`、`session-starting-loader.tsx`、`sandbox-loading-boundary.tsx`  
- **聊天核心**：`session-chat.tsx`（数千行：turns、乐观发送、permission/question）  
- **作曲器**：`composer-chat-input.tsx`、`session-chat-input`  
- **工具渲染**：`tool/tools/register.ts` 注册 bash/read/write/grep/task/connector/executor/dcp-compress…  
- **恢复语义**：`session-resume.ts`、`session-terminal-state.ts`  

工具渲染器**只画 UI**，不执行工具——执行在沙箱 OpenCode（第 7 章）。

### A.5 Review Center 双入口不要混

| 入口 | 性质 | 证据 |
|---|---|---|
| `/review` | 可点的 **mock 原型**，无 API/auth | `review/page.tsx:8-18` |
| 项目 Customize → Review | **Connected** 真数据 | `review-view.tsx:4-32`；`review-center-connected.tsx` |

`ReviewCenterConnected` 还会：

- 拉 `useReviewItems`；  
- 映射 API → inbox view model（`mapApiReviewItem`）；  
- executor 审批走 `resolveApproval`（与会话内 approval prompt 同客户端）；  
- CR 走 merge/close/request-changes（`:88-148`）；  
- `canAct=false` 时只读（`:46-49`）。

侧栏 `project-change-requests-nav.tsx` 与 session list 会读 `useReviewCenterEnabled` / `useReviewSessionSummary`，把待审数量露在导航上。

---

## 加深织入 B：Snapshot / Provider / Boot 细读（补强第 3/6 章）

### B.1 Provision 与 builder 的调用链

`session-sandbox.ts:1-12`：插入 `session_sandboxes`（主键 = 调用方 UUID = session id）后 fire-and-forget。后台会走 `ensureSandboxImage`（从 `builder.ts` 导入，`session-sandbox.ts:39-44`）再 `getProvider().create()`。

Builder 的 `SnapshotBuildSource`（`builder.ts:56-62`）说明镜像不只在「第一次开会话」时建：

- `project-create`：建项目就预热；  
- `cr-merge`：清单/Dockerfile 变更后重建；  
- `session-start`：缺镜像时补建；  
- `manual` / `background` / `startup`：运维与预热路径。

### B.2 Provider 单一真相

`provider-parity.test.ts:29-31`：`config.KNOWN_PROVIDERS` 必须与 `SANDBOX_TEMPLATE_PROVIDERS` 集合相等。列出的四家：`daytona`、`platinum`、`e2b`、`local-docker`（`:11,34`）。本地开发可用 `local-docker` 且不要求云 API key（`:70-74`）。

选择/均衡：`session-sandbox.ts` import `selectProvider`、`readActiveRouting`、`providerFallbackSetting`——支持路由切换与 fallback，而不是写死一家厂商。

### B.3 Boot-by-template-id

`sessionBootByTemplateIdEnabled`（`session-sandbox.ts:161-169`）默认 ON：按已激活的 pinned template id 启动，仅在 GC'd-pin 404 时回退 name-boot。可用 env `KORTIX_SESSION_BOOT_BY_TEMPLATE_ID=0` 紧急关掉——这是生产 rollout 的 escape hatch。

### B.4 沙箱镜像边界（再强调）

`apps/sandbox/README.md:15-28`：镜像只有运行会话所需；triggers/channels/connectors/secrets **不在镜像**。这与 MANIFESTO「密钥注入、连接器走 proxy」（`MANIFESTO.md:62-63`）一致，也解释了为何改 daemon 要 rebuild snapshot（反馈环偏慢）。

### B.5 `openSession` 叫醒冬眠

`openSession` 不只读库：stopped + 有 `externalId` 时可能 `resumeStoppedSandbox`（保留 disk/workspace）；没有 usable box 时可在 open 路径上 provision。因此 `continueSession` 的 5 分钟等待窗口（`READY_DEADLINE_MS`，`engine.ts:39`）覆盖的是「resume 或新建」的真实世界时间，不是单纯的自旋。

---

## 加深织入 C：Change Request 前后端对照表（补强第 10 章）

### C.1 状态机

```mermaid
stateDiagram-v2
    [*] --> open: POST change-requests / kortix cr open
    open --> merged: POST .../merge
    open --> closed: POST .../close
    closed --> open: POST .../reopen
    open --> open: POST .../request-changes\n(metadata + 可选投递 agent)
```

枚举定义：`kortix.ts:2726-2730`。合并后写 `merged_at` / `merged_by` / `merge_commit_sha`（表字段，`kortix.ts:2757+`）。

### C.2 「Request changes」不是空注释

`recordRequestedChange`（`change-requests.ts:85-95`）把人类笔记 append 进 `metadata.requested_changes`。API `…/request-changes`（`r8.ts:641-643`）在持久化后可尝试把 prompt 送回 originating session（投递失败只 warn，不丢笔记——见 r8 中 `request-changes prompt not delivered` 日志路径）。

Web Connected 层：无 note 时 toast 提示先写说明（`review-center-connected.tsx:128-134`）；成功时根据 `res.delivering` 区分「已送给 agent」与「仅保存」（`:139-144`）。

### C.3 CLI ↔ API 映射

| CLI（`cr.ts`） | 大致 API |
|---|---|
| `cr ls` | GET `.../change-requests` |
| `cr show` | GET `.../change-requests/:id` |
| `cr diff` | GET `.../diff` |
| `cr open` | POST `.../change-requests` |
| `cr merge` | POST `.../merge` |
| `cr close` / `reopen` | POST `.../close` / `.../reopen` |

沙箱内免登录：环境变量 `KORTIX_CLI_TOKEN` + `KORTIX_PROJECT_ID`（`cr.ts:38-40`）。注意 `KORTIX_TOKEN` 是 sandbox service key，**不是** CLI token（同处注释）。

### C.4 与 GitHub PR 的产品差异

CR 层不绑定单一托管商（`change-requests.ts:4-8`；`kortix.ts:2720-2724`）。权限、通知、Review Center 可长在 Kortix IAM 上。代价是「在 GitHub UI 里点 Merge」不会自动同步 Kortix CR 状态——除非走 Kortix merge API（或后续集成；本分析不臆造未找到的双向同步）。

### C.5 E2E / 文档锚点

- API 脚本：`apps/api/scripts/e2e-change-requests.sh`  
- CLI 脚本：`apps/cli/scripts/e2e-cr.sh`  
- 流程测试：`tests/src/flows/change-requests.flow.ts`  
- 用户文档：`apps/web/content/docs/work/change-requests.mdx`  
- 设计文档引用：`docs/REVIEW_CENTER_DESIGN.md`（Connected 头注释，`review-center-connected.tsx:14`）

---

## 加深织入 D：四个 packages 再展开（补强第 2/5/10 章）

### D.1 `@kortix/manifest-schema`

- 包描述：CLI ship 预检 + 后端 CR-merge gate + validate 共用（`package.json:4`；`src/index.ts:5-11`）。  
- 纯函数：`(rawToml: string | object) → ManifestValidationResult`（`index.ts:14-15`）。  
- 常量面很宽：channel platforms、connector auth/policy、sandbox CPU/内存/磁盘边界、trigger types、grantable CLI actions（`index.ts:22-36` 从 `./constants` 导入）。  
- v2 agent 校验拆到 `index.v2.ts`（`index.ts:42-48`）。  
- `connector-headers` 独立模块：禁止某些 header 名、限制长度/数量——API parser 与 executor **共享同一规则**（`index.ts:64-66`）。

### D.2 `@kortix/starter`

- `getStarterFiles()` 走目录 + `{{var}}` 替换，返回 `{ path, content }[]`（`src/index.ts:4-9`）。  
- 用户默认模板：`general-knowledge-worker`；`minimal` 仅内部 clone seed（`index.ts:36-48`）。  
- 编译进 CLI 二进制时用 `embedded.generated.json` 兜底（`index.ts:20-27`）——源码树编辑模板即可热更新，compiled 二进制不丢模板。

### D.3 `@kortix/registry`

- 提供 `buildRegistry` / `loadItem` / `loadRegistry` / validate（`src/index.ts:1-12` 导出列表）。  
- **故意没有**确定性 install engine：marketplace 安装改为 agent 读源合并文件并开 CR（`index.ts:8-11`）。这与「公司即 git + CR 纪律」一致——安装本身也是一次可审变更。

### D.4 `@kortix/executor-sdk`

- Elastic-2.0 可发布包（`package.json:5-6`）。  
- `ExecutorClientOptions`：有 `projectId` 时走 project-explicit 路由，user token 与 sandbox session token 同构（`src/index.ts:41-50`）。  
- 结果可带 `pending_approval` + `execution_id`（`index.ts:28-38`）——与 Review Center executor 行、会话内 approval UI 同一业务对象。

---

## 加深织入 E：平台投递与宿主发送的对照（补强第 4/6 章）

### E.1 控制面投递（渠道 / 自动化）

```text
continueSession
  → wait ready (≤300s)
  → deliverWithRetry (≤45s, interval 1.5s)
  → postPrompt → POST .../prompt_async
```

`deliver.ts:2-11` 说明动机：旧路径第一次 404/5xx 就告诉 Slack 用户「再说一次」并丢消息；现在在 webhook 已 ack 的前提下继续愈合。

### E.2 宿主发送（Web 会话内）

`SessionChat` 使用 `sendAndRecover` / `beginOptimisticSend` / `classifySendError` 等 SDK 原语（`session-chat.tsx:142-154`），消息真相来自 `useSessionSync(sessionId)`（`:3482-3491`）。计费 402 会变成 `KortixSendError` kind `billing`（`use-session.ts:96-107`），页面可弹 upgrade。

两者最终都落到 OpenCode 的 prompt/stream，但：

- 渠道路径强调 **服务端 heal**；  
- Web 路径强调 **乐观 UI + SSE 同步**。

### E.3 代理链（SSE 回程）

1. `forwardToSandbox` / 宿主经 `/v1/p/<external>/8000/...`  
2. sandbox-proxy 鉴权  
3. daemon 反代 `opencode serve`  
4. SDK `openEventStream`（框架无关，可在 worker 复用）管理超时、heartbeat、重连退避  

AGENTS.md 写明代理 URL 形如 `http://localhost:8008/v1/p/<external_id>/8000/...`，SSE 在 `…/event`（`AGENTS.md:184-186`）。

---

## 加深织入 F：与 OpenManus / nanobot 主循环对照表（补强第 6/12 章）

| 步骤 | OpenManus | nanobot | Kortix/Suna |
|---|---|---|---|
| 收消息 | stdin | Channel → MessageBus | Webhook/CLI/SDK/`useSession` → session-lifecycle |
| 拼上下文 | Memory 列表硬截断 | system+memory+history | repo md + env + OpenCode transcript + manifest |
| 调模型 | `LLM.ask_tool` | AgentRunner + 自研 provider | OpenCode → LLM Gateway |
| 调工具 | 进程内 ToolCollection | 进程内 tools/MCP | 沙箱本地工具 + 服务端 Executor |
| 结束 | `terminate` 工具 | 模型停或策略 | 用户 stop / 沙箱回收；持久化靠 CR |
| 权限 | 几乎开放 | 工具级 + UNTRUSTED | IAM + grant + 守秘 + CR + Review |
| 多租户 | 无 | 单人多渠道 | 账户/项目/会话/沙箱四层 |
| 前端 | 几乎无 | WebUI SPA | Next Command Center + Desktop/Mobile |

把这张表贴在第 6 章旁边，就不会再问「为什么 API 里找不到 ReAct」。

---

## 加深织入 G：初学者调试清单（实战向）

当你觉得「Agent 没反应」时，按层排查：

1. **API health**：`curl localhost:8008/v1/health`（`AGENTS.md:193`）  
2. **会话行**：`project_sessions.status` 是 provisioning / running / failed？  
3. **沙箱行**：`session_sandboxes.status` 是否 active？`external_id` 有无？（主键应等于 session id）  
4. **代理**：`/v1/p/<external>/8000/kortix/health` 是否 daemon=ok 且 opencode=ok？  
5. **OpenCode id**：`opencode_session_id` 是否为空？Web 看 `useSession(...).opencodeSessionId`  
6. **投递**：API 日志是否 `prompt_async non-ok` / deliver deadline（`engine.ts:687-695`；`deliver.ts`）  
7. **Web 计费门**：`noPlan` 是否挡住了 `enabled`？（`page.tsx:83-97`）  
8. **权限**：connector 是否被 policy `block`？Review Center 是否有 pending approval？  
9. **CR**：Agent 说改好了但 main 没变——是否只 commit 未 `kortix cr open` / 未 merge？  
10. **Manifest**：`kortix ship` / validate 是否被 `@kortix/manifest-schema` 拦住？

---

## 加深织入 H：完整检出后仍明确「未找到」的清单

诚实标出边界，避免把 OpenCode 上游能力算进本仓：

| 项 | 状态 |
|---|---|
| 本仓内 ReAct `think/act` 循环 | **未找到**（在 OpenCode） |
| Dream / 自动公司脑压缩服务 | **未找到**（MANIFESTO 愿景） |
| 平台级统一 prompt cache 服务 | **未找到** |
| `/review` 生产 API | **无**（mock only） |
| Registry 确定性 install engine | **已移除**（改 agent import） |
| OpenCode 上游 auto-compact 实现细节 | 不在本仓，不断言 |

---


---

## 写作说明

- **分析基准（完整检出加深版）**：commit `5862e95671cfd198c6a94ceb9c1fefe579f00a8b`；版本以 `VERSION`=`0.10.14` 为准（README badge `0.9.98` 不一致已标明）。  
- **核对方式**：关键路径均打开源码核对；`apps/web`、全部 `packages/*`、desktop/mobile **已纳入**。OpenCode 上游完整源码仍不在本仓，相关结论标「未找到」或「执行在 OpenCode 内」。  
- **相对旧稀疏版**：删除所有「未检出 apps/web」表述；把 command center / `useSession` 挂载、CR 前后端全路径、snapshot builder / providers、四个新 packages 织进正文各章，而不是堆在附录。  
- **产品对照**：与 OpenManus / nanobot / Claude Code 的对比基于公开定位与架构差异，不断言闭源实现细节。

*全文完。*
