From 09d968cb2fefa4acf64aa44201a2044ba511f9c1 Mon Sep 17 00:00:00 2001 From: weichen Date: Thu, 8 Oct 2026 08:37:04 +0000 Subject: [PATCH 1/2] Add architecture guide set for overview, intent, runs, and squads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: 系统缺少面向新人与新任务的现状导览;把结构/意图识别/任务执行/小队 四条主线的架构、模型与调用链整理成五篇参考文档,降低跨仓定位成本。 Evidence: 锚点核验于 Dev 组合 218fc4b1ba00b0a03b5bb79f292b3eba6c987a58 (2026-10-08,引用的函数入口行号逐一 grep 定位);git diff --check 通过; 五篇互链相对路径全部可解析。SRC=SELF(本地核验,任务卡有记录)。 Impact: 纯文档新增(docs/guides 五篇),无代码/契约/配置影响。 Refs: Dev 任务分支 feature/architecture-guides(R0 218fc4b1)。 --- docs/guides/architecture-overview.md | 213 +++++++++++++++++++++ docs/guides/intent-recognition.md | 177 +++++++++++++++++ docs/guides/squad-and-subagents.md | 131 +++++++++++++ docs/guides/squad-mechanics.md | 142 ++++++++++++++ docs/guides/task-planning-and-execution.md | 199 +++++++++++++++++++ 5 files changed, 862 insertions(+) create mode 100644 docs/guides/architecture-overview.md create mode 100644 docs/guides/intent-recognition.md create mode 100644 docs/guides/squad-and-subagents.md create mode 100644 docs/guides/squad-mechanics.md create mode 100644 docs/guides/task-planning-and-execution.md diff --git a/docs/guides/architecture-overview.md b/docs/guides/architecture-overview.md new file mode 100644 index 0000000..16b4203 --- /dev/null +++ b/docs/guides/architecture-overview.md @@ -0,0 +1,213 @@ +# Agent-System 系统结构与架构 + +> 基准:Agent-System-Dev 组合 `218fc4b1ba00b0a03b5bb79f292b3eba6c987a58`(R0,2026-10-08 锚点核验)。 +> 行号为该提交下的函数入口位置(个别内部区段为约略范围),随代码演进漂移;文件与函数名是稳定锚点。 +> 文中路径均为 Dev 组合仓内相对路径。 + +--- + +## 1. 总体形态:一个父仓 + 12 个子模块仓的组合系统 + +Agent-System 是**多仓组合(gitlink 固定指针)**的开发集成父仓,不是单一代码库。 +`.gitmodules` 定义了 12 个子仓,分两类: + +**modules/(5 个产品模块仓)** + +| 仓 | 目录 | 定位 | 技术栈 | +|---|---|---|---| +| Agent-System-Core | `modules/core` | 核心契约库:纯数据类型 + 线格式权威,零第三方依赖 | Go(附 `ts/types.ts` TS 镜像) | +| Agent-System-Backend | `modules/backend` | 后端模块化单体,默认单二进制(内嵌前端 + 嵌入式 SQLite) | Go | +| Agent-System-Frontend | `modules/frontend` | Web 前端 | React 19 + Vite + TypeScript | +| Agent-System-UI | `modules/ui` | 纯展示组件库:props 进 events 出,不持状态不调 API | React + CSS 令牌 | +| Agent-System-Documents | `modules/documents` | 文档过程仓(设计提案/记录/报告),无代码 | Markdown | + +**deps/(7 个基础库仓)** + +| 仓 | go module 名 | 一句话职责 | +|---|---|---| +| agent-native | `agent-native` | Agent 执行内核:ReAct 循环、LLM 客户端、工具注册表、会话转录、MCP、技能 | +| agent-squad | `agent-squad` | 小队编排底座:规划器、Leader 委派、黑板、HITL 挂起恢复、调度 | +| agent-intent | `agent-intent` | 确定性意图识别:四级漏斗(Guard→Expectation→Rules→Adjudicate) | +| agent-command | `agent-command` | 命令总线基础库:命令 spec、危险分级、审计信封(零依赖) | +| error-code-system | `error-code-system` | 跨系统错误码目录 + HTTP 错误信封 + 查询服务 | +| logging-system | `logging-system` | 结构化日志库 + 采集服务 | +| development-guidelines | —(非 Go) | 人与 Agent 共用的开发规范文档基线 | + +**依赖方向铁律**(`README.md`): + +``` +frontend → core ← backend → { agent-native, agent-squad, agent-intent, agent-command } + ↘ error-code-system / logging-system +``` + +core 与各生态仓互不依赖;backend 对 8 个库的引用经自己仓内 `deps/` 子模块 + +`modules/backend/go.mod` 的 `replace` 收敛(父仓顶层 `deps/` 与 `modules/backend/deps/` +是同一批仓的两份 gitlink,`scripts/check-pins.sh` 核对一致)。仓库**不使用 go.work** +(避免工作区最小版本选择抬高 backend 单独构建的依赖版本,见 `PROJECT.md`)。 + +**三态一致的契约机制**:core 的每个契约同时存在 ① Go 类型(camelCase JSON)、 +② `modules/core/testdata/golden/*.json` 黄金样本(线格式权威)、③ `modules/core/ts/types.ts` +手工 TS 镜像。父仓 `make contracts` 把 TS 镜像生成到 frontend 与 ui 的 `src/contracts.ts`。 + +--- + +## 2. Backend 功能分布(产品核心) + +单二进制入口:`modules/backend/cmd/agent-system/main.go`(子命令 serve/version/apikey/user/config)。 +组合根与装配顺序在 `modules/backend/internal/app/app.go` 的 `Run()`: +存储 → 总线 → 工具注册 → settings/prompts/skills/profiles → runmgr(三个 runner + SquadRunner 注入)→ +intentgate → 插件宿主 + hostapi → workflow 注册表 → RAG → 可选 intentsem → +cardsvc/cmdbus → server + 限流 + 认证 → HTTP 监听(默认 `127.0.0.1:8787`)。 + +### internal 包职责一览(功能 → 代码分布) + +| 包 | 主文件 | 职责 | +|---|---|---| +| app | `internal/app/app.go` | 组合根:装配一切组件 | +| config | `internal/config/config.go` | 启动配置单一管道:内置默认→`--config` JSON→环境变量→CLI | +| store | `internal/store/store.go` | 持久化唯一入口;SQLite(默认,modernc 纯 Go)/ PostgreSQL 双档;`dialect.go` 方言收敛、`migrate.go` 版本化迁移 | +| blobstore | `internal/blobstore/blobstore.go` | 工件对象存储抽象,默认本地目录 `/artifacts/` | +| bus | `internal/bus/bus.go` | 进程内事件总线:按会话转发已持久化事件给 SSE 订阅者 | +| pgbus | `internal/pgbus/pgbus.go` | PG 档跨实例扇出:`NOTIFY/LISTEN agent_events` | +| cmdbus | `internal/cmdbus/cmdbus.go` | 命令总线宿主:button/card/slash/chat 四入口归一分发、危险命令确认卡、审计 | +| cardsvc | `internal/cardsvc/cardsvc.go` | 收件箱任务卡服务(card.* 事件投影) | +| runmgr | `internal/runmgr/runmgr.go` | **Run 生命周期枢纽**:状态机、事件持久化+广播、HITL、子代理任务树、工具执行策略、取消级联 | +| route | `internal/route/route.go` | 唯一意图 Router:命令总线 > 工作流 > 普通回合 | +| intentgate | `internal/intentgate/gate.go` | 意图闸门:接入 agent-intent 库,插件意图 overlay | +| intentsem | `internal/intentsem/scorer.go` `generator.go` | 意图慢通道:embedding 语义重排 + 轻量模型命令生成(默认关) | +| runtime | `internal/runtime/runner.go` `llm.go` `llm_direct.go` `sub_loop.go` `workflow_compiled.go` `squad_runner.go` `model.go` | 四类 Runner(LLM/Mock/Sub/Squad)+ 工作流执行入口 + 模型路由 | +| wfcompile | `internal/wfcompile/run.go` `compiler.go` | 工作流编译执行驱动:编译图 → 小队引擎装配 → 步进循环 → critic 终判 | +| ctxasm | `internal/ctxasm/ctxasm.go` `finish.go` | 上下文组装与预算裁剪 | +| prompts | `internal/prompts/service.go` | 提示词模块库:embed 内置 + DB 用户覆盖链 | +| skills | `internal/skills/skills.go` | 内置技能种子(go:embed recipes → `/skills/`) | +| tools | `internal/tools/registry.go` | 统一工具注册表:内置/声明式 HTTP/插件工具同形态 | +| plugins | `internal/plugins/host.go` `host_v2.go` | 插件宿主:fail-closed 热加载;sidecar 进程拉起 | +| hostapi | `internal/hostapi/server.go` | sidecar 本机回调面 `/host/v1`(127.0.0.1 随机端口 + 随机 token) | +| workflow | `internal/workflow/registry.go` | 工作流**定义**注册表(编译执行器在 wfcompile) | +| ingest / docgen / rag / quality | 各主文件 | 文档摄取 → ParsedDocument IR;Markdown→docx;混合检索 rerank;交付物六维打分 | +| profiles / settings / ratelimit | 各主文件 | 画像;system→user→project 分层设置;租户限流(内存/Redis) | + +### 其他目录 + +- `modules/backend/pkg/extension/`:对下游仓的**公开扩展面**(下游只许依赖本包与 core, + `surface.txt` 锁导出面);`link/` 把 extension.Main 接回组合根。 +- `modules/backend/web/embed.go`:`go:embed` 内嵌前端构建产物。 +- `modules/backend/deps/`:8 个库的 submodule 副本(replace 目标)。 + +--- + +## 3. 前端与 UI 分工 + +- **frontend**(`modules/frontend/src/`):`App.tsx` 四栏布局 —— 左(项目/会话树 + `TaskSidebar.tsx`)、中(流式对话 `Conversation.tsx` + `ProcessTimeline.tsx`)、 + 预览区(`PreviewPane.tsx` / `ArtifactsPanel.tsx`)、右(工作台卡流 `Workbench.tsx`)。 + 职能文件:`api.ts`(HTTP 客户端)、`live.ts`(SSE 到达序投影纯逻辑)、 + `store.ts`、`runstate.ts`、`pluginHost.ts`、`outbox.ts`。 +- **ui**(`modules/ui/src/`):只渲染词汇有限的 UI schema —— `UIRenderer.tsx`、 + `InboxList.tsx`、`InlineConfirmCard.tsx`(危险命令确认卡)、`ToolBlock.tsx`、 + `tokens.css`(设计令牌唯一来源)。frontend 经 `@ui` 别名直接源码组合(`vite.config.ts`)。 + +--- + +## 4. 通信架构:HTTP + SSE,无 WebSocket + +- **全部 HTTP 路由**集中在 `modules/backend/internal/server/server.go` 的 `Handler()` + (约 90 条 `mux.HandleFunc`):auth/users、projects/conversations/messages、cards、 + artifacts/uploads、profiles/prompts/settings、tools/plugins、commands/audit、 + plan/todos、knowledge、hitl 等。 +- **SSE** 是唯一增量通道:`GET /api/events?conversationId=...`(`server.go:171` 注册, + `server.go:275` 处理器),带 `Last-Event-ID`/`since` 游标重放;项目级流用伪会话键 + `project:`(`modules/core/events.go:30` `ProjectChannelKey`)。全仓 grep 无 websocket。 +- **事件三层转发**: + 1. `runmgr/events.go` 事件缓冲:高频 delta 合并(150ms/64 条,`events.go:42-43`),先落 + store(会话内单调 seq)再 `bus.Publish`(`events.go:83`); + 2. `internal/bus` 内存总线 → 在线 SSE 订阅者; + 3. `internal/pgbus`(仅 PG 多实例档):事务提交后 NOTIFY,其他实例 LISTEN 后按 seq 从库 + 取回转发本地 bus。 +- **事件词汇**全部定义在 `modules/core/events.go` 的 `StreamEvent` 信封 + (message.delta / thinking.delta / tool.* / run.phase / hitl.request / plan.update / + agent.spawn / agent.progress / card.* / artifact.* 等 20+ 类型)。 + +--- + +## 5. 持久化与磁盘布局 + +- 主库:`/agent-system.db`(SQLite,FTS5 全文 + CJK LIKE 兜底)或 PostgreSQL + (`--dsn`,pgx 驱动);迁移双方言可回滚;租户隔离靠 owner 列硬过滤。 +- 工件:`/artifacts/`(blobstore 本地档,Put 原子落盘)+ store 登记。 +- 技能:`/skills/`(内置种子 + 用户覆盖)。 +- 工作区沙箱:`/workspaces///`(fs 工具根)。 +- 小队运行态:编排目录下 `runs//*.json`(磁盘即权威,见小队篇)。 +- 本机服务实例(`scripts/svc.sh` 管理):`runtime/dev/`(agent 后端 + SQLite)、 + `runtime/pg1/`(PostgreSQL);`data/`、`data-test/` 为默认/测试数据目录。 + +--- + +## 6. 插件系统 + +- 契约:`modules/core/plugin.go`(manifest v1:tools/workflows/intents/prompts/panels/ + cards/commands 等 provides);`modules/core/plugin_v2.go`(2.0-experimental 窗口: + PluginScope 可见性、PluginBackend{inproc|sidecar}、技能/MCP/知识集/解析器/代理声明)。 +- 宿主:`modules/backend/internal/plugins/host.go` —— 「manifest + 声明资产 + 可选代码」, + 任一资产解析失败整批拒绝(fail-closed),支持热加载 `Reload`;`host_v2.go` 拉起 sidecar + 进程(注入 `AGENT_HOST_API`/`AGENT_HOST_TOKEN` 环境变量)。 +- sidecar 回调经 `internal/hostapi/server.go` 的 `/host/v1`(kv/audit/commands/llm/search); + 进程内插件走 `pkg/extension`(EXT-1)+ `link` 包。 +- 示例:父仓 `plugins/example/`、`examples/sidecar-demo/`、`examples/downstream-rdm/`。 + +--- + +## 7. 端到端数据流(用户输入 → 结果返回) + +``` +用户输入(中栏) + │ POST /api/conversations/{id}/messages server.go:574 postMessage + ├─ "/" 前缀 → cmdbus 命令总线(不启 run,server.go:623) + ├─ 附件 → ingest.Parse 文档摄取(并行,互不依赖,server.go:643) + ├─ 意图识别 → intentgate(agent-intent 四级漏斗,server.go:655) + ├─ 路由裁决 → route.Decide route.go:60 + │ ├─ KindCommand → cmdbus.Dispatch(直接执行命令) + │ ├─ KindWorkflow → runmgr.StartClaimedWorkflowRun(:996) + │ └─ KindTurn → runmgr.StartClaimedConversationRun(:991) + ├─ runmgr 建 Run(pending)落库 → 选 Runner(runmgr.go:918)→ go execute() + │ ├─ LLMRunner → agent-native 内核 ReAct 循环(默认) + │ ├─ CompiledWorkflowRunner → wfcompile 编译执行(可由规划子代理生成) + │ └─ SquadRunner → squadadapter → agent-squad 引擎 + ├─ 一切增量皆事件 → runmgr 缓冲 → store(seq 权威)→ bus → SSE + └─ 前端 live.ts 到达序投影 → 中栏对话 / 右栏卡流 / AgentTracker +``` + +- HITL:`hitl.request` 卡片(choice/input/confirm/form)→ 用户 `POST /api/hitl/{id}/resolve` + → `runmgr.ResolveHITL`(runmgr.go:1261)恢复或转轨。 +- 工件:产物落 blobstore + store → `artifact.created` 事件 → 预览区;quality 六维打分。 + +--- + +## 8. 设计文档索引 + +架构权威文档在 `modules/documents/docs/proposals/`: + +- 整体:`2026-09-29-system-skeleton-design-and-plan.md`(骨架设计 + D1–D16 决策,主设计)、 + `2026-09-30-dual-tier-storage-and-multitenancy.md`(双档存储/多租户)、 + `2026-10-01-base-extension-roadmap.md`(基座不可改、下游二开)。 +- Agent 栈:`2026-10-01-agent-harness.md`(回合循环、多代理编排)、 + `2026-10-02-agent-stack-completion.md`(通用智能体/小队/意图)、 + `2026-10-07-agent-stack-n1q1i1-plans.md`、`n2-plan.md`、`n3-plan.md`(N 系列阶段方案)。 +- 其他:卡片流/收件箱/命令总线/工作区(2026-09-30 三篇)、插件契约 v2、UI 重构等。 + +--- + +## 9. 关键文件速查 + +| 想了解什么 | 看哪里 | +|---|---| +| 程序入口与装配 | `modules/backend/cmd/agent-system/main.go`、`internal/app/app.go` | +| 全部 HTTP 路由 | `modules/backend/internal/server/server.go`(`Handler()`) | +| Run 状态机与事件 | `modules/core/model.go`、`modules/core/events.go`、`internal/runmgr/runmgr.go` | +| Agent 执行循环 | `deps/agent-native/runtime/loop.go`、`internal/runtime/llm_direct.go` | +| 意图识别 | `deps/agent-intent/intent/`、`internal/intentgate/gate.go`、`internal/route/route.go` | +| 小队引擎 | `deps/agent-squad/squad/`、`internal/squadadapter/adapter.go`、`entry.go` | +| 工作流 | `internal/workflow/registry.go`(定义)、`internal/wfcompile/`(编译执行)、`internal/runtime/workflow_compiled.go`(入口) | +| 工具体系 | `modules/core/tools.go`、`internal/tools/registry.go`、`internal/runmgr/runmgr.go:740` | +| 提示词/技能 | `internal/prompts/service.go`、`internal/skills/skills.go` | +| 前端投影 | `modules/frontend/src/live.ts`、`src/store.ts` | diff --git a/docs/guides/intent-recognition.md b/docs/guides/intent-recognition.md new file mode 100644 index 0000000..11ff777 --- /dev/null +++ b/docs/guides/intent-recognition.md @@ -0,0 +1,177 @@ +# Agent 的意图识别:架构、模型与调用链 + +> 基准:Agent-System-Dev 组合 `218fc4b1ba00b0a03b5bb79f292b3eba6c987a58`(R0,2026-10-08 锚点核验)。 +> 行号为该提交下的函数入口位置(个别内部区段为约略范围),随代码演进漂移;文件与函数名是稳定锚点。 +> `internal/` 指 `modules/backend/internal/`。 + +--- + +## 1. 一句话结论 + +意图识别是**"确定性规则漏斗为主体、语义向量与 LLM 为可选慢通道(默认关闭)"的混合 +分层架构**:识别主体在独立库 `deps/agent-intent`(零 LLM 依赖),backend 通过 +`internal/intentgate` 闸门接入,识别结果由 `internal/route` 统一裁决为 +命令 / 工作流 / 普通回合三分支。 + +--- + +## 2. 识别架构:四级漏斗 + +核心识别器 `FunnelRecognizer`(`deps/agent-intent/intent/recognizer.go:23`, +入口 `Recognize` 在 `recognizer.go:41`,内部 `recognize` 在 `:71`),四级依次短路: + +| 级 | 名称 | 行为 | 代码 | +|---|---|---|---| +| Stage 0 | 守卫(guard_hit) | 守卫域 spec 命中即短路,表达"识别即拒绝" | `recognizer.go` 守卫分支 | +| Stage 1 | 挂起预期(expectation_hit) | 多轮澄清场景:宿主注入 `ExpectationProvider` 声明上轮预期意图 | 接口 `intent/expectation.go:17` | +| Stage 2+3 | 规则计分 + 置信度裁决 | 全部非守卫 spec 线性扫描(`intent/registry.go` 规则计分与裁决),`score = base + (正则命中 ? pattern_bonus : 0)`,上限 1.0;唯一过阈或前两名分差 ≥ margin(默认 0.1)→ **matched**,同分比 priority → 否则 **ambiguous**,无命中 → **fallback** | `intent/spec.go:17-29`(默认值)、`recognizer.go:146` 附近同分裁决 | +| Stage 4 | 慢通道(可选) | ambiguous → 语义向量重排 `semanticRerank`(`recognizer.go:292`);fallback → LLM 命令生成 `generateCommands`(`recognizer.go:333`) | 见下节 | + +识别分支共五种(`deps/agent-intent/intent/result.go`): +`guard_hit / expectation_hit / matched / ambiguous / fallback`。 + +--- + +## 3. 用什么模型:三层各自不同 + +| 层 | 是否 LLM | 实现与模型来源 | 状态 | +|---|---|---|---| +| 规则层(守卫/计分/裁决) | **否,永不引入** | 纯 Go 确定性计算。设计文档明确规则层永不引入 LLM(`deps/agent-intent/docs/records/2026-09-29-decoupling-and-llm-layering.md` §2.1) | **主体,默认生效** | +| 语义重排(SemanticScorer) | 向量模型(非生成式) | `modules/backend/internal/intentsem/scorer.go:18` `EmbeddingScorer`:对输入与 spec 的 examples 各取向量算最大余弦相似度。Provider 走 OpenAI 兼容 `/embeddings`(`internal/rag/embed.go`),模型/端点来自 settings `models.embedding.*`;默认阈值 0.8(`intent/spec.go`) | 可选,**默认关**(settings `runtime.intentSemantic`) | +| 命令生成(CommandGenerator) | 生成式 LLM | `modules/backend/internal/intentsem/generator.go:18` `LightGenerator`:走 `agentllm.Client.Stream`,用**轻量档模型**(`internal/runtime/model.go:84` `ResolveLightweightModel`,settings `models.lightweight.model`,缺省回落主 chat 档),MaxTokens 默认 1024;system prompt 在 `generator.go:100-129`(只暴露 id/描述/槽位键,防提示词注入) | 可选,**默认关**(settings `runtime.intentGenerator`) | +| Classifier(LLM 重打分,L2) | 生成式 LLM | 接口在 `intent/classifier.go:9` | **接口预留,backend 未接线** | + +慢通道装配点:`modules/backend/internal/app/app.go:351,357`(开关开启才 +`gate.SetSemantic(...)` / `gate.SetGenerator(...)`)。关闭时行为与纯规则版完全一致。 + +**失败安全**:agent-intent 库本体零 LLM 依赖,只定义 `SemanticScorer` +(`intent/semantic.go:9`)与 `CommandGenerator`(`intent/generator.go:14`)窄接口由 +宿主注入;所有慢通道失败均回退规则结论(fail-open/closed 到 safe 分支),识别永不因 +LLM 失败而失败。生成器幻觉有校验兜底:`validateGenerated` +(`recognizer.go:368`)要求 action.id/槽位键必须都在目录内,否则 fail-closed 回 fallback。 + +--- + +## 4. 意图目录(Intent Catalog / Schema) + +意图定义 `IntentSpec`(`deps/agent-intent/intent/spec.go:74` 附近,Go 注册与 JSON overlay 共用): + +- **标识**:`id`(必须 `domain.助记` 多段,`spec.go:122` 校验)、`domain`、`version`、 + `description`、`priority`、`disabled`。 +- **matcher**(`spec.go:32-39`):`includes_any / includes_all / excludes_any` 词表 + + `patterns` 正则 + `case_sensitive` + `boundary`(词界匹配,压制中文子串误命中)。 +- **slots**(`spec.go:44-62`):`key/required/pattern(抽取正则)/group`,加 HITL 字段 + `label/question`(建议澄清提问)与校验链 `enum/format/normalize/validator`。 +- **confidence**(`spec.go:66-70`):base 默认 0.7、pattern_bonus 默认 0.2、threshold 默认 0.6。 +- **动作绑定**:`action`(kind ∈ command/workflow/blueprint/handler,`intent/command.go:32-43`) + 优先,其次 `metadata["blueprint"]`、再次 `handler`,否则 auto(宿主在命令总线与工作流间裁决)。 +- **risk**(low/medium/high)+ `requires_confirm`(透传宿主做授权确认)。 +- **examples**:示例句数组——语义慢通道按它们算相似度。 + +配置文件形态(`intent/loader.go:12-19`): + +```json +{ "intents": [ { …IntentSpec… } ], "disable": ["demo.run.status_query"] } +``` + +插件可经 `intents/*.json` overlay 注入(`internal/plugins/host.go:216-220` 采集)。 +样例:`deps/agent-intent/examples/specs/demo.json`。 + +识别产物命令集(`intent/command.go:11-26`):`start / set_slot / change_slot / cancel / +clarify / chitchat / handoff`;规则漏斗只产出 start/set_slot/clarify(`deriveCommands` +在 `command.go:85`)。能力目录 `Snapshot.Catalog()` / `CatalogMarkdown()` +(`intent/catalog.go:36,68`)把启用意图渲染为 Markdown,供小队规划者/Leader 提示词注入 +(`deps/agent-squad/adapters/intent/catalog.go` 消费)。 + +--- + +## 5. 端到端调用链:用户输入一条消息后发生了什么 + +| # | 步骤 | 调用方 → 被调方 | 位置 | +|---|---|---|---| +| 1 | HTTP 入口 | 路由注册 `POST /api/conversations/{id}/messages` | `internal/server/server.go:168`(注册)、`:574` `postMessage` | +| 2 | slash 短路 | `/` 前缀非技能命令 → `handleCommand`(`server.go:756`)→ 命令总线,**不进意图识别** | `server.go:623` | +| 3 | 附件摄取 | `ingest.Parse(name, mime, content)` → `core.ParsedDocument`(与意图识别并行、互不依赖) | `server.go:643`;`internal/ingest/ingest.go` | +| 4 | 识别调用 | `s.recognize(ctx, text)`(端口 `NLIntentGate`,`server.go:79-85`) | `server.go:655` | +| 5 | 进入闸门 | `intentgate.Gate.Recognize` | `internal/intentgate/gate.go:137` | +| 6 | 四级漏斗 | `FunnelRecognizer.Recognize(ctx, input, reg.Snapshot())` | `gate.go:144` 附近 → `deps/agent-intent/intent/recognizer.go:41` | +| 7 | 结果投影 | 库 `Result` → `MatchResult{Branch, ID, Slots, Missing, Commands}`;guard/expectation 折叠为 fallback;缺槽位附 spec 的 Question | `gate.go:148-176` | +| 8 | 统一路由 | `route.Decide(recognized, route.Env{HasCommand, WorkflowFor, SquadReady})` | `server.go:656`;`internal/route/route.go:60` | +| 9a | 命令分支 | KindCommand → `s.Bus.Dispatch`(Entrance="chat",槽位透传 Params) | `server.go:701-717` | +| 9b | 工作流分支 | KindWorkflow → `mgr.StartClaimedWorkflowRun`(缺槽位列表随行) | `server.go:725`;`internal/runmgr/runmgr.go:996` | +| 9c | 回合分支 | KindTurn → `ClaimConversation` 抢槽 → `AppendMessageWithRequest` 落库 → `mgr.StartClaimedConversationRun` | `server.go:679, 727`;`runmgr.go:980, 991` | +| 10 | 澄清/HITL | 工作流缺必填槽位 → 逐个发 `HITLCard{Kind:Input, AllowFreeInput}` 人工输入卡 | `internal/wfcompile/run.go:47` 起 `RunCompiled` 槽位补齐段 | +| 11 | HITL 转轨 | `POST /api/hitl/{id}/resolve` → `mgr.ResolveHITL`:自由输入以 `/` 开头 → pivot=command;否则再过 `gate.Recognize` + `route.Decide`,命中其他意图则取消挂起 run 并 pivot | `server.go:883`;`runmgr.go:1261`(转轨判定 `:1293` 附近) | + +### 启动期装配链 + +| # | 内容 | 位置 | +|---|---|---| +| A1 | `app.Run` 建 `gate := intentgate.New()` 并 `mgr.SetGate(gate)` | `internal/app/app.go:309-310`;`runmgr.go:142` | +| A2 | 插件装载回调:`pluginHost.OnIntents = overlay → gate.Rebuild(overlay)`(fail-closed:解析失败保留上一快照) | `app.go:324`;`internal/plugins/host.go:216-220`;`gate.go:96` | +| A3 | 开关开启时接线慢通道:`gate.SetSemantic(intentsem.EmbeddingScorer{…})` / `gate.SetGenerator(intentsem.LightGenerator{…})` | `app.go:351,357`;`gate.go:66,75` | + +--- + +## 6. intentgate 与 intentsem 的分工 + +- **intentgate**(`internal/intentgate/gate.go`):宿主闸门适配器。持 Registry + + FunnelRecognizer;负责插件 overlay 装载(`Rebuild`,`:96`)、分支折叠(非 + matched/ambiguous 一律折叠为 fallback,`gate.go:148-155`)、补槽位 Question、 + 类型投影(让 runmgr/route 只依赖本包表面)、慢通道接线(`:66,75`)。 + **不做缓存**——识别是纯函数 Query,快照不可变无锁读(`intent/registry.go`)。 +- **intentsem**(`internal/intentsem/`):慢通道实现包。`EmbeddingScorer` 出错原样上抛 + (库保持 Ambiguous);`LightGenerator` 解析失败/模型不可达返回错误(库保持 fallback)。 + +--- + +## 7. 识别结果的去向与失败路径 + +- 识别结果 `Result`(`intent/result.go`)是**纯内存 Query 结果,不落库**(store 无 + intent 表)。去向仅三处:① HTTP 响应;② logsys 结构化日志 `intent recognized` + (`recognizer.go:62` 附近,不记原文);③ 作为 run 启动参数(workflow slots/missing、命令 ID)。 +- **ambiguous**(重排后仍歧义)→ gate 折叠 → `route.Decide` 返回 `KindTurn` + (`route.go:61-63`)→ 走普通 LLM 回合自由对话兜底(不是强制澄清)。 +- **matched 但缺必填槽位** → Commands=[start, clarify](`intent/command.go:110` 附近)→ + workflow 路径把 Missing(含建议 Question)传入 run → 发 HITL 输入卡澄清。 +- **guard_hit / expectation_hit** → 折叠为 fallback → turn。 +- **低置信度**(未过 threshold)→ fallback → turn。 + +route 决策细节(`internal/route/route.go:55-114`):优先级 +命令 > 工作流 > 普通回合;`blueprint` → `KindSquad`,但**当前不可达**—— +`server.go:664` 明确注释 `SquadReady 保持 false`(per-run entry 覆盖未就位), +小队的实际启动走别的路径(见[小队与子 Agent 启动](squad-and-subagents.md))。 +声明目标不可解析时不静默错配,回落 turn 并写 Reason。 + +--- + +## 8. 辅助工具与测试 + +- `deps/agent-intent/cmd/intentctl/`:只读诊断 CLI(`lint` 校验配置+词条冲突、 + `explain` 单条输入裁决轨迹(`recognizer.go:226` `Snapshot.Explain`)、 + `eval` 语料评测 Precision/Recall、`catalog` 输出目录)。 +- `deps/agent-intent/conformance/conformance.go`:识别器公开契约行为验收 + (分支/槽位断言 + 确定性 + 并发冒烟)。 +- `deps/agent-intent/catalogcheck/align_test.go`:断言库错误码与 Error-Code-System + intent 域种子码逐个相等,防两套平行权威。 + +--- + +## 9. 关键文件清单 + +| 文件 | 职责 | +|---|---| +| `deps/agent-intent/intent/recognizer.go` | 四级漏斗识别器、语义重排(:292)、生成兜底(:333)、Explain 诊断(:226) | +| `deps/agent-intent/intent/spec.go` | IntentSpec/MatchSpec/SlotSpec/ConfidenceSpec 定义与校验 | +| `deps/agent-intent/intent/registry.go` | 编译、不可变 Snapshot、双源合并、原子 Reload | +| `deps/agent-intent/intent/loader.go` | JSON overlay 加载 | +| `deps/agent-intent/intent/command.go` | 命令模型 + deriveCommands(:85)展开 | +| `deps/agent-intent/intent/catalog.go` | 能力目录与 Markdown 渲染(:36/:68/:128) | +| `deps/agent-intent/intent/semantic.go` `generator.go` `classifier.go` `expectation.go` | 四个可注入端口 | +| `deps/agent-intent/cmd/intentctl/main.go` | 诊断 CLI | +| `modules/backend/internal/intentgate/gate.go` | 闸门适配器(overlay :96、投影、慢通道接线 :66/:75) | +| `modules/backend/internal/intentsem/generator.go` `scorer.go` | LLM 命令生成(含 system prompt :100)/ embedding 重排 | +| `modules/backend/internal/route/route.go` | 唯一 Router(Decide :60 三分支裁决) | +| `modules/backend/internal/server/server.go` | 消息入口 postMessage(:574)、recognize(:655)、resolveHITL(:883)、SSE events(:275) | +| `modules/backend/internal/plugins/host.go` | 插件 intents/*.json overlay 采集(:216-220) | +| `modules/backend/internal/app/app.go` | 组合根装配(:309-310、:324、:351/:357) | diff --git a/docs/guides/squad-and-subagents.md b/docs/guides/squad-and-subagents.md new file mode 100644 index 0000000..3196710 --- /dev/null +++ b/docs/guides/squad-and-subagents.md @@ -0,0 +1,131 @@ +# 何时启动小队或子 Agent:触发条件与调用链 + +> 基准:Agent-System-Dev 组合 `218fc4b1ba00b0a03b5bb79f292b3eba6c987a58`(R0,2026-10-08 锚点核验)。 +> 行号为该提交下的函数入口位置(个别内部区段为约略范围),随代码演进漂移;文件与函数名是稳定锚点。 +> `internal/` 指 `modules/backend/internal/`。 + +--- + +## 1. 一句话结论 + +系统有两条"委派"线,都发生在**普通 run 的执行过程中**(小队不是独立的一层): + +- **子代理(sub-agent)**:模型在回合中调用 `delegate_task` 工具 → runmgr 派生 + `Kind=subagent` 的子 run(任务树)。 +- **小队(squad)**:三条触发路径 —— ① 设置 `runtime.entry=squad` 让**对话本身** + 走 SquadRunner;② 模型调用 `squad_orchestrate` 工具;③ 演示消息含 `#squad`。 + 三者最终都汇入 `deps/agent-squad` 引擎。**成员执行统一走 agent-native 内核** + (legacy 的 agent-system 子 run 压缩路径已退役,`internal/squadadapter/adapter.go:289-292` + 注释明示;后端缺席时 fail-closed,不静默降级)。 + +另有第三种"规划委派":`run_workflow` 触发的**规划子代理**(workflowPlannerID), +见[任务规划与执行](task-planning-and-execution.md)第 7 节。 + +--- + +## 2. 子代理:`delegate_task` 触发链 + +### 触发条件 + +主对话模型判断某段工作适合独立完成(如长调研、批量整理)时,在 ReAct 循环里发出 +`delegate_task` 工具调用(工具名常量 `internal/runtime/llm.go:58`)。工具声明与门禁在 +`internal/runtime/llm.go:267-315`(编排型工具随上下文声明;子代理 spec 由 prompts 包提供 +tools/model/maxTurns 约束,**禁止子代理再持有派发类工具**,`internal/prompts/service.go`)。 + +### 调用链 + +| # | 步骤 | 位置 | +|---|---|---| +| 1 | 内核循环解析出 `delegate_task` 调用 → `runTool` 分派 | `internal/runtime/llm_direct.go:471` `runTool`,case 在 `:493` 起 | +| 2 | 后台模式:经 rc 端口断言调 `SpawnBackground`;前台:`SpawnAgent` | `llm_direct.go:502, 516` 附近 | +| 3 | runmgr 派生子 run | `internal/runmgr/runmgr.go:1348` `spawnSubagent` → `:1358` `startChild`(**深度检查**在此,注释 `:1356`) | +| 4 | 建 `Kind=subagent` 子 run,`ParentRunID` 指向父 run(**任务树**) | `runmgr.go` startChild 内;契约 `modules/core/model.go:188` | +| 5 | 发 `agent.spawn` 事件(前端 AgentTracker 可见) | `runmgr.go:1375` | +| 6 | 独立 ctx 执行(后台模式 detach) | `runmgr.go` startChild 内 | +| 7 | 子代理执行器 = `SubLLMRunner.Execute` —— 同样进入 `nruntime.Run` 内核循环(独立转录、按子代理 spec 收窄工具) | `internal/runtime/sub_loop.go:31` | +| 8 | 完成回填 `AgentResultPayload`;结果截断 32000 字 | `runmgr.go:1401` `finishChild`;`runmgr.go:1780` `capSubagentResult` | + +### 后台子代理 + +- `delegate_task` 可请求后台模式 → `spawnBackground`(`runmgr.go:1475`), + **每会话上限 3 个**(`runmgr.go` 常量)。 +- 后台子代理完成后不直接打扰当前回合,而是进通知队列:`deliverNotices` + (`runmgr.go:1560`)在会话空闲时以 system-reminder 消息启动一个新父回合汇报结果 + (`startNoticeRun`,`runmgr.go:1616`)。 +- 取消级联:子 run 的 ctx 派生自父 run,`CancelRun`(`runmgr.go:1222`)取消根 run + 即级联取消全部子 run。 + +--- + +## 3. 小队:三条触发路径 + +### 路径 A:`squad_orchestrate` 工具调用(工具路径) + +| # | 步骤 | 位置 | +|---|---|---| +| A1 | 模型在回合中调用 `squad_orchestrate goal=...` | 工具名常量 `internal/runtime/llm.go:62`;声明 `llm.go:268` 附近 | +| A2 | 声明门禁:settings `runtime.squad`(默认 true)开启才声明;**entry 已是 squad 时不再提供,避免套娃** | `internal/runtime/llm.go:467` `squadToolEnabled`;`:266` 注释 | +| A3 | runTool 分派 → `squadadapter.Orchestrate(rc, dataDir, goalText)` | `internal/runtime/llm_direct.go:479` → `internal/squadadapter/adapter.go:47` `Orchestrate` | +| A4 | 之后进入 agent-squad 引擎(规划→调度→质检) | 见[小队工作机制](squad-mechanics.md) | + +### 路径 B:对话入口模式(entry=squad) + +| # | 步骤 | 位置 | +|---|---|---| +| B1 | 灰度开关 `runtime.entry=squad`(默认 `direct`) | `internal/settings/settings.go:90` | +| B2 | runmgr 选 Runner:`selectConversationRunner` 检查 `runtime.entry=="squad"` → 返回 SquadRunner | `internal/runmgr/runmgr.go:918`(注入点 `:908` `SetSquadRunner`,装配 `internal/app/app.go:306`) | +| B3 | `SquadRunner.Execute`(`internal/runtime/squad_runner.go:26`)先做 **LLM 分诊**:`modelRouter.Route`(`:146`,轻量模型输出 JSON `direct|plan`,`parseRoute` :167)—— direct = 单步 assistant 直通(`deps/agent-squad/squad/router.go:55` `directAssistantPlan`);plan = 进 Leader 委派(`router.go:29` `applyRoute` 设 `metadata["mode"]="leader"`) | | +| B4 | plan 模式 → `squadadapter.RunEntry` → 引擎 leader 模式 | `internal/squadadapter/entry.go:42` `RunEntry`;`deps/agent-squad/squad/engine.go`(`RunOptions.Mode="leader"`,Options 注释 `engine.go:99-105`) | + +### 路径 C:演示/脚本 + +用户消息含 `#squad` → MockRunner 走同样 `Orchestrate`(`internal/runtime/mock.go`)。 + +### 一条"不可达"的路径(设计预留) + +意图识别绑定 `blueprint` 动作可路由到 `KindSquad`(`internal/route/route.go:96-97`), +但当前 `server.go:664` 明确注释 `SquadReady 保持 false`(per-run entry 覆盖未就位), +**该路径暂不可达**——小队的意图目录注入另走 `deps/agent-squad/adapters/intent/catalog.go`。 + +--- + +## 4. 装配(启动期) + +| 内容 | 位置 | +|---|---| +| runmgr 注入三个 runner(LLMRunner / SubLLMRunner / MockRunner),SquadRunner 经 `SetSquadRunner` | `internal/app/app.go:287-306` | +| 角色指令来源接提示词库(`agent.squad.`) | `app.go:176` → `squadadapter.RoleInstructions`(`adapter.go:303`) | +| agent-native 内核选项:模型路由、Tavily 搜索、解释器、MCP、技能根、Subagent 桥接、PermissionAsk、检查点 | `app.go:196` `squadadapter.AgentNativeOptions` | +| 小队后端解析器:**只注册 `"agent-native"` 内核工厂**(legacy `"agent-system"` SpawnSubagent 压缩路径已退役删除;工厂缺席解析 fail-closed) | `internal/squadadapter/adapter.go:292` `BuildBackends`(退役注释 `:289-291`);`entry.go:103` DefaultBackend="agent-native" | +| 内核工厂注册 | `deps/agent-squad/adapters/native/register.go:61` `RegisterFull` | + +--- + +## 5. 两条线的对比小结 + +| | 子代理 delegate_task | 小队 squad | +|---|---|---| +| 触发者 | 主对话模型 | 设置开关 / 主对话模型 / 演示脚本 | +| 执行单位 | 一个子 run(`Kind=subagent`) | 一组角色化步骤(每步一个 agent-native 内核 agent) | +| 编排者 | 主对话(单层委派,子代理不可再委派) | agent-squad 引擎(DAG 调度 / Leader 循环) | +| 并发 | 后台最多 3/会话 | 无写集冲突的步骤可并行 | +| 深度 | 1 层(startChild 深度检查,`runmgr.go:1356-1358`;子代理 spec 禁派发工具) | 成员内核里的 `delegate_task` 经 `SubagentFactory`(`adapter.go:234`)桥回产品 `SpawnSubagent`,**深度恒为 1,子 run 不再嵌套小队** | +| 汇总 | `AgentResultPayload` 回父 run | 交付物聚合 `output.md` → 主 run 工件(见[小队工作机制](squad-mechanics.md)) | + +--- + +## 6. 关键文件清单 + +| 职责 | 文件 | +|---|---| +| delegate_task 分派 | `internal/runtime/llm_direct.go:493` 起 | +| 子 run 派生与任务树 | `internal/runmgr/runmgr.go:1348-1475`、`1560-1616` | +| 子代理执行器 | `internal/runtime/sub_loop.go` | +| 工具声明与门禁 | `internal/runtime/llm.go:267-315, 466-467` | +| squad_orchestrate 分派 | `internal/runtime/llm_direct.go:479` | +| 小队适配器(工具路径 + 后端组装 + Subagent 桥接) | `internal/squadadapter/adapter.go`(Orchestrate :47、SubagentFactory :234、BuildBackends :292、builtinRoles :306) | +| 小队适配器(入口路径) | `internal/squadadapter/entry.go`(RunEntry :42、kernelRoles :177) | +| 对话入口 Runner + LLM 分诊 | `internal/runtime/squad_runner.go`(Execute :26、Route :146) | +| Runner 选择 | `internal/runmgr/runmgr.go:918` | +| 灰度开关 | `internal/settings/settings.go:90` | +| 装配 | `internal/app/app.go:176, 196, 287-306` | diff --git a/docs/guides/squad-mechanics.md b/docs/guides/squad-mechanics.md new file mode 100644 index 0000000..dbb33c0 --- /dev/null +++ b/docs/guides/squad-mechanics.md @@ -0,0 +1,142 @@ +# 小队(Squad)工作机制:拆解、执行、汇总的完整调用链 + +> 基准:Agent-System-Dev 组合 `218fc4b1ba00b0a03b5bb79f292b3eba6c987a58`(R0,2026-10-08 锚点核验)。 +> 行号为该提交下的函数入口位置(个别内部区段为约略范围),随代码演进漂移;文件与函数名是稳定锚点。 +> `internal/` 指 `modules/backend/internal/`;`squad/` 指 `deps/agent-squad/squad/`。 + +--- + +## 1. 一句话结论 + +小队是 run 内的一种**编排执行模式**:由 agent-squad 引擎(`deps/agent-squad`)把目标 +拆成**角色化步骤 DAG**(规则规划器或蓝图模板),每一步交给一个"成员"执行——成员是 +按角色装配的 **agent-native 内核 agent**(独立执行循环;legacy 的 agent-system 子 run +压缩路径已退役),无写集冲突的步骤并行推进,产出落子工作区并经**黑板**共享,收口时做 +六维质检与自动修复,交付物聚合回主 run。 + +触发时机(何时启动):settings `runtime.entry=squad`(对话即小队)、模型调用 +`squad_orchestrate` 工具、演示消息 `#squad`——详见[小队与子 Agent 启动](squad-and-subagents.md)第 3 节。 + +--- + +## 2. 概念模型(类型全部在 `deps/agent-squad/squad/models.go`) + +| 类型 | 位置 | 含义 | +|---|---|---| +| `RoleSpec` 角色 | models.go:25 | role/name、绑定执行后端 `Backend`(stub/llm/agent-native/…)、角色级 Model、工具白名单 Tools、技能 Skills、**BashPermission 三态 allow/ask/deny**、Keywords(规则规划器匹配用)、Instructions、HITL 策略、`MaxConcurrentTasks` 并发限流 | +| `Goal` | models.go:48 | 目标文本 + Materials 附件 + Constraints + HITL 覆盖 | +| `Blueprint` | models.go:64 | 任务模板:关键词命中即走基线步骤 | +| `StepSpec` 步骤 | models.go:86 | Role、Targets(期望交付物文件,缺省 output.md)、`Writes` 写集(写集相交强制串行)、`Needs` 依赖、`Parallel`、`Critical`(失败即终止)、`MinScore` 质检门槛、`RequiresApproval`、`IntentID/IntentSlots`(系统功能步,不走智能体)、Attempts | +| `WorkflowPlan` + `SquadConfig` | models.go:168, 179 | 拆解结果(与产品 squad workflow.json / squad.json 兼容) | +| `BlackboardState` 黑板 | models.go:225 | Facts/Gaps/Implications/Assumptions/Fields —— 成员间协作的共享状态载体 | + +**小队成员 ≠ 常驻 agent**:成员 = 角色(RoleSpec)。每条步骤由 +`BackendResolver.Resolve`(`squad/backend.go`)解析出一个 Executor 并调用一次 +`Execute`。产品侧**只注册 agent-native 后端**(`internal/squadadapter/adapter.go:292` +`BuildBackends`;`deps/agent-squad/adapters/native/register.go:61` `RegisterFull`), +每步构造一个独立内核 agent 执行循环(模型/工具/搜索/MCP/技能根按 RoleSpec 装配)。 + +--- + +## 3. 小队 Run 状态机 + +`squad/models.go:284` 起(`RunStatus`): + +``` +created → planning → (awaiting_clarification | awaiting_confirmation) ← HITL 挂起 + → executing → (waiting_human | quality_review) ← HITL 挂起 + → completed | degraded | failed | cancelled ← 终态 +``` + +- 挂起态判定与适配:适配器 `isHoldStatus`(`internal/squadadapter/adapter.go:221`)据此进 HITL。 +- 挂起上下文 `HitlGate`(Request+Plan+PrevStatus)落盘 `pending.json`;`Respond` + (`squad/engine.go:637`)按多种 Kind 分派恢复:Clarification 答案并入 Goal 重新规划、 + Confirmation、Gap、Escalation、Adjustment、Permission、QualityReview;执行态恢复走 + `Resume`(`squad/engine.go:1127`)。 + +**事件体系**:引擎 `squad.Event` 13 类(`squad/progress.go:10-24`:run_started / +phase / step_started / completed / failed / skipped / detail / deliverable / +human_input_required / human_responded / run_completed / failed / cancelled)。 +产品侧不直连 bus:`onSquadEvent`(`internal/squadadapter/adapter.go:185`)把 step_* 映射为 +`core.EvAgentProgress`,计划经 `mapPlan`(`adapter.go:200`)映射为 `rc.UpdatePlan` → +`core.EvPlanUpdate`,成员子 run 发 `core.EvAgentSpawn` —— 统一进事件流,**前端可看到 +小队进展**(`modules/frontend/src/store.ts`、`contracts.ts` 消费 agent.spawn/plan.update/agent.progress)。 + +**运行状态持久化(磁盘即权威)**:`workspace.json / workflow.json / squad.json / +blackboard.json / pending.json / runstate.json / usage.json` +(`squad/runstate.go:13` 注释、`:81` `persist`);崩溃恢复 `Resume → recoverInterrupted` +(产物已齐判完成防副作用翻倍)。支持运行中 Steering 与黑板字段注入自动唤醒 `SweepWakeups`。 + +--- + +## 4. 端到端调用链(以 `squad_orchestrate` 工具路径为主线) + +| # | 步骤 | 位置 | +|---|---|---| +| 1 | 触发:`llm_direct.go:479` case toolSquad → `squadadapter.Orchestrate` | `internal/runtime/llm_direct.go:479` → `internal/squadadapter/adapter.go:47` | +| 2 | 编排进度卡(可选):创建 `CardKindProgress` 卡,终态置态 | adapter.go `Orchestrate` 内(CardSink 端口 `:40`) | +| 3 | 注册内置角色 `builtinRoles()`(`adapter.go:306`):规划员/调研员/撰稿员/审校员,**全部 Backend=agent-native**;工具白名单按角色最小化(全员含 write_file,步骤须自己落盘交付物);撰稿员 `BashPermission=ask` + docx/xlsx/pptx-author 技能,其余角色 Bash=deny;指令来源 `RoleInstructions`(:303,接提示词库 `agent.squad.`) | | +| 4 | 组装后端 `BuildBackends`(`adapter.go:292`):只注册 `"agent-native"` 内核工厂(组合根 `AgentNativeOptions`,`internal/app/app.go:196`);legacy `"agent-system"` 已退役;工厂缺席 fail-closed | | +| 5 | 组装引擎 `squad.NewEngine(Options{…})`:Planner=RulePlanner、Checker=quality.Checker、RepairBudget、HitlPolicy、Sink=ProgressFunc(onSquadEvent);引擎默认 `DefaultBackend="stub"`(`squad/engine.go:169-170`)、单步超时默认 10 分钟(Options 注释 `:32`) | `adapter.go` Orchestrate 内;`squad/engine.go` | +| 6 | `SquadEngine.Run`(`squad/engine.go:234`):生成 `run_` ID → `acquire`(`:213`,进程内 mutex + flock 文件锁,单 run 单控制操作)→ `EvRunStarted` → 可选**意图闸门**一次裁决(GuardHit→Reject;命中 blueprint→定向模板;否则不干预;实现 `deps/agent-squad/adapters/intent/gate.go`) | | +| 7 | **任务拆解(规划)** `planAndExecute`(`squad/engine.go:335`):先 Router 分诊;未 direct 则蓝图匹配 + `Planner.Plan`(`squad/planner_rule.go:20`)。产品用 **RulePlanner(确定性规则,非 LLM)**:命中 Blueprint 直接用模板步骤/编制(`fromBlueprint`,planner_rule.go:27);未命中走四阶段骨架 **梳理(organizer)→专项(specialist)→成文(writer)→终审(leader)**(planner_rule.go:85 起),注册表缺角色时 `Registry.Derive` 现场派生并置 `NeedsConfirmation`。LLM 规划器已实现(`squad/planner_llm.go`,严格 JSON+重试+Fallback)但产品未接。拆解结果持久化 `runs//workflow.json + squad.json`(`squad/runstate.go:81` `persist`) | | +| 8 | 澄清/确认挂起:规划歧义 → `awaiting_clarification`;派生编制 → `awaiting_confirmation`(leader 模式豁免)。适配器驱动循环(`adapter.go` Orchestrate 内):hold 态 → `askHuman`(`adapter.go:150`,HumanRequest→core.HITLCard→`rc.AskHITL`)→ `engine.Respond`(`engine.go:637`);执行态 → `engine.Resume`(`engine.go:1127`)。每次推进后 `mapPlan`(`adapter.go:200`)把 WorkflowPlan 映射为右栏计划快照 | | +| 9 | **工作区物化**:`PrepareWorkspace`(`squad/workspace.go:27`)+ `BuildSubWorkspaces`(workspace.go:127):每步建 `tasks/NN_/` 子工作区,把 `context/`(goal.md、材料原文 + textextract 抽取文本、index.json)与 template.md 快照进去(`snapshotContextInto` workspace.go:160),并写 **TASK.md**(`writeTaskFile` workspace.go:182:黑板快照、权威等级资源表、产出要求——成员靠它读前序产出)与 **AGENTS.md**(`writeAgentsFile` workspace.go:224:角色指令) | | +| 10 | **调度执行** `flowDriver.advance`(`squad/workflow.go:25`):每轮从权威 Steps 现场编译调度图 `compileGraph`(`squad/graph.go:23`:Needs 依赖或相邻 Parallel 同层,环 fail-closed)→ 分并行/串行层 → **无写集冲突的步骤并行**(`runParallel` workflow.go:216,goroutine+WaitGroup,关键失败 cancel 全组),写集冲突按声明序串行 → `gateBeforeRun`(workflow.go:175:bash ask 角色授权 / 步骤审批的 HITL 门)→ `runOneStep` | | +| 11 | **单步执行** `runOneStep`(`squad/workflow.go:262`):置 running、Attempts++ → 写 TASK.md → 组装 ExecRequest(ToolAllowlist/Skills/SessionScope/Budget/OnEvent 过程帧)→ 后端 `Execute`(系统功能步改走 IntentRunner)→ 瞬态失败重试并注入重试提示 → **交付物落盘校验** `missingTargets`(workflow.go:1071)→ 完成:`mergeStepArtifacts` 把子工作区 `fields.json/gaps.json` 并入黑板(`squad/blackboard.go:43`)→ `checkBlockingGaps`(workflow.go:652:interactive 挂 HitlGap 卡、auto 记兜底假设继续)→ 每步后可动态调整(评估器加步/跳步)。superstep 边界写检查点快照,Resume 时校验图指纹防形态漂移 | | +| 12 | **成员执行(统一 agent-native)**:`RegisterFull`(`adapters/native/register.go:61`)按角色构造独立内核 agent(模型/工具/搜索/MCP/技能根按 RoleSpec+BashPermission 装配),过程帧经 eventTap 桥为 `EvDetail`。内核里的 `delegate_task` 经 `SubagentFactory`(`adapter.go:234`,SpawnBackground `:266` / SpawnSubagent `:273`)桥回产品 SpawnSubagent,**深度恒为 1,子 run 不再嵌套小队** | | +| 13 | **Leader 委派模式**(entry=squad 的 plan 分诊):`RunOptions.Mode="leader"`(`squad/engine.go:99-105` Options 注释;轮次上限 `MaxLeaderTurns` 默认 8,`squad/leader.go:48` `DefaultMaxLeaderTurns`)——规划被委派循环取代:每轮重写 leader 的 TASK.md(目标+黑板+委派历史)与 AGENTS.md(协调契约),leader 后端执行产出 **delegation.json**(action=delegate/escalate/close/failed);delegate:临时建 StepSpec、跑 runOneStep、交付快照进 leader 的 `inbox//`、再唤醒 leader 评估(一轮只委派一个成员);escalate → HitlEscalation 挂起;超限按现有产出降级收口。核心状态与提示词装配:`LeaderState`(leader.go:57)、`DelegationRecord`(:81)、`delegation`(:91)、`leaderPrompt`(:126)、`leaderExecRequest`(:16) | | +| 14 | **汇总与质检(收口)** `finalize`(`squad/workflow.go:784`):最后 completed 步骤的主产出复制为 `output.md` → `Checker.Check`(`internal/quality/checker.go:16` 附近 → `score.go`:六维启发式评分——结论先行/结构/完整性/证据/可执行性/可读性;**Passed = 总分≥70 且无单维<40**,score.go:28,72;docx 走段落提取;可选 housestyle lint)→ 未过写 `quality-feedback.md`(checker.go:29)并自动修复 `rerunForRepair` 重跑交付步骤(workflow.go:913)→ 仍不过:interactive 转 `quality_review` 人工裁决(approve=manualPass、retry 追加预算但受上限 `MaxRepairGranted`,`squad/termination.go:14`)→ 写 `manifest.json`(全文件 sha256 防篡改)→ `EvRunCompleted` | | +| 15 | **聚合回主 run**:`Orchestrate` 返回 summary,交付物经 `rc.WriteArtifact("squad-.md")` 登记为主 run 工件;`RunEntry` 返回 `EntryResult{Summary, Body}`(`entry.go:34`),SquadRunner 做**虚假交付检查**(`internal/runtime/squad_runner.go:95` 附近 `claimsFalseDeliverable`)后落工件并 `SetAssistantParts`(squad_runner.go:107)。所有 usage 按角色聚合进 `RunResult.Usage` | | + +--- + +## 5. 小队如何"协作" + +- **黑板(Blackboard)**是协作中枢:Facts(已确认事实)/Gaps(信息缺口)/ + Implications/Assumptions/Fields。每步完成后子工作区的 `fields.json`/`gaps.json` + 合并进黑板(`squad/blackboard.go:43` `mergeStepArtifacts`);下一成员的 TASK.md 带 + 黑板快照,据此读前序产出。字段注入可自动唤醒挂起的 run(`SweepWakeups`)。 +- **材料溯源**:输入材料经 `deps/agent-squad/textextract/` 提取纯文本并注入页码锚点, + 成员引用材料时可标注出处。 +- **防失控上限**:`squad/termination.go` 集中管理修复预算、Leader 轮数等终止条件。 + +--- + +## 6. 与 runmgr / workflow 的关系 + +- 小队**不是独立的一层**:它是 conversation run 内的一种执行模式/入口(`runtime.entry`) + 或 run 内 LLM 的一次工具调用,在 runCtx 上同步驱动。 +- 成员执行的子 run 经产品 `SpawnSubagent` 进任务树(仅限内核 `delegate_task` 桥接); + 工作流(`run_workflow` → wfcompile 编译执行)是另一条平行机制——注意 wfcompile 执行时 + **也装配到 squad 引擎上步进**,entry=squad 时不再提供 `run_workflow` 声明(互斥, + `internal/runtime/llm.go:266-268`)。 +- 依赖方向:agent-squad 核心只依赖 error-code-system(不感知内核);依赖由 adapters + 层单向引入——`agent-squad/adapters/native` 同时依赖 agent-native 与 agent-squad; + backend require 两者并 replace 到自己 `deps/` 下的库副本。 + +--- + +## 7. 关键文件清单 + +| 职责 | 文件 | +|---|---| +| 全部类型与状态机 | `deps/agent-squad/squad/models.go`(RoleSpec :25、StepSpec :86、BlackboardState :225、RunStatus :284) | +| 门面(Run/Respond/Resume/Inspect/Steer) | `deps/agent-squad/squad/engine.go`(Run :234、acquire :213、planAndExecute :335、Respond :637、Resume :1127) | +| 调度/单步/收口 | `deps/agent-squad/squad/workflow.go`(advance :25、gateBeforeRun :175、runParallel :216、runOneStep :262、checkBlockingGaps :652、finalize :784、rerunForRepair :913、missingTargets :1071) | +| DAG 编译与图指纹 | `deps/agent-squad/squad/graph.go`(compileGraph :23) | +| Leader 委派 | `deps/agent-squad/squad/leader.go`(DefaultMaxLeaderTurns :48、LeaderState :57、leaderPrompt :126) | +| 规划器(规则/LLM/端口) | `squad/planner_rule.go`(Plan :20、fromBlueprint :27)、`planner_llm.go`、`planner.go` | +| direct/plan 分诊 | `squad/router.go`(applyRoute :29、directAssistantPlan :55) | +| 状态持久化(磁盘即权威) | `squad/runstate.go`(persist :81) | +| 工作区/TASK.md/AGENTS.md | `squad/workspace.go`(PrepareWorkspace :27、BuildSubWorkspaces :127、writeTaskFile :182、writeAgentsFile :224) | +| 黑板合并 | `squad/blackboard.go`(mergeStepArtifacts :43) | +| 事件 | `squad/progress.go`、`store/eventsink.go` | +| 后端解析器 / 防失控上限 | `squad/backend.go`、`termination.go` | +| backend 工具路径适配 | `internal/squadadapter/adapter.go`(Orchestrate :47、askHuman :150、onSquadEvent :185、mapPlan :200、isHoldStatus :221、SubagentFactory :234、BuildBackends :292、builtinRoles :306) | +| backend 入口路径适配 | `internal/squadadapter/entry.go`(RunEntry :42、kernelRoles :177) | +| 对话入口 Runner + LLM 分诊 | `internal/runtime/squad_runner.go`(Execute :26、Route :146) | +| 质检打分(六维) | `internal/quality/checker.go`、`score.go` | +| 内核接成小队执行器 | `deps/agent-squad/adapters/native/register.go`(RegisterFull :61) | +| 意图闸门/能力目录 | `deps/agent-squad/adapters/intent/gate.go`、`catalog.go` | +| 独立 CLI(serve/run/demo) | `deps/agent-squad/cmd/agent-squad/` | diff --git a/docs/guides/task-planning-and-execution.md b/docs/guides/task-planning-and-execution.md new file mode 100644 index 0000000..0ae08ef --- /dev/null +++ b/docs/guides/task-planning-and-execution.md @@ -0,0 +1,199 @@ +# Agent 的任务规划与执行:架构与调用链 + +> 基准:Agent-System-Dev 组合 `218fc4b1ba00b0a03b5bb79f292b3eba6c987a58`(R0,2026-10-08 锚点核验)。 +> 行号为该提交下的函数入口位置(个别内部区段为约略范围),随代码演进漂移;文件与函数名是稳定锚点。 +> `internal/` 指 `modules/backend/internal/`;`core/` 指 `modules/core/`; +> `nruntime/` 指 `deps/agent-native/runtime/`。 + +--- + +## 1. 一句话结论 + +系统有**两套并存的规划机制**: + +1. **默认主线 = LLM 自主规划(ReAct 循环)**:没有预置 DAG。普通消息由 backend + `LLMRunner` 驱动,进入 `deps/agent-native` 自研内核的单轮循环 + (组装上下文 → 调 LLM → 解析工具调用 → 执行工具 → 回填 → 直到模型不再调工具), + 模型可用 `update_plan` / `update_todos` 工具自主维护任务列表(右栏 Plan/Todos 面板)。 +2. **可选结构化路径 = WorkflowDef(数据化步骤列表)**:可由意图绑定启动,也可由模型 + 调用 `run_workflow goal=...` 触发"规划子代理生成步骤 → 用户 HITL 确认 → 编译执行" + (`internal/runtime/workflow_compiled.go:91` `planCompiledWorkflow`)。工作流的 + 编译与步进执行在 `internal/wfcompile` 包,执行时**装配到小队引擎上步进** + (挂起 → HITL 卡 → Respond;执行态 → Resume),收口带 critic 终判。 + +执行主体分两层:backend 的 `runmgr`(Run 生命周期/事件/持久化/策略)+ +`internal/runtime`(各 Runner);真正的智能体循环内核在 `deps/agent-native/runtime/loop.go`。 + +--- + +## 2. Run 生命周期与状态机 + +契约定义在 `modules/core/model.go`: + +- `RunStatus`(model.go:166): + `pending → running → (waiting_hitl ⇄ running) → completed | failed | cancelled | interrupted` +- `RunPhase`(model.go:178):`thinking → acting → responding → finalizing → done` +- `RunKind`(model.go:158):`conversation | subagent | tool`; + `Run` 结构体(model.go:188)含 `ParentRunID`(任务树)、Usage、Error。 + +状态迁移代码位置(均在 `internal/runmgr/runmgr.go`): + +| 迁移 | 代码位置 | +|---|---| +| 创建(pending) | `:1023` `startClaimedRun`(建 Run + `store.CreateRun`,`internal/store/messages.go:120`) | +| 运行 | `:1055` 附近 `execute`(置 running/thinking) | +| HITL 挂起/恢复 | `:449` `AskHITL` 置 waiting_hitl;`:1261` `ResolveHITL` 恢复 running | +| 终态 | `:1087` `finishRun` → `internal/store/recovery.go:125` `SettleRun`(条件 UPDATE + 同事务写权威助手消息树,防并发双终态)→ 发 `run.completed/failed` | +| 取消 | `:1222` `CancelRun`(根 run ctx 取消,子 run 派生自父 ctx 天然级联;并唤醒 HITL 等待) | +| 停机中断/重启对账 | `:1655` `InterruptRunning`、启动时 `:1689` `ReconcileInterrupted` | +| 会话并发控制 | `:980` `ClaimConversation`(一会话至多一个在途 run,忙返回 409,`server.go` 错误映射) | + +--- + +## 3. 端到端调用链(普通消息 → 结果落库) + +| # | 步骤 | 位置 | +|---|---|---| +| 1 | HTTP 入口 `postMessage`:附件经 `ingest.Parse`;`/` 命令不启 run;意图识别 + `route.Decide` 三分支 | `internal/server/server.go:574`;路由调用 `:656` | +| 2 | 抢会话槽位 + 消息落库(幂等 requestId) | `server.go:679` → `runmgr.go:980`;`server.go:686` 附近 `AppendMessageWithRequest` | +| 3 | 启动 run:工作流回合 `StartClaimedWorkflowRun`(定义深拷贝进 Launch,插件卸载不影响在跑副本);普通回合 `StartClaimedConversationRun` → `startClaimedRun`:建 Run 落库 → 注册 runHandle → `selectConversationRunner` → 附件原件落工作区 → `go m.execute(rc, runner)` | `server.go:725,727`;`runmgr.go:991, 996, 1023-1043`;runner 选择 `runmgr.go:918` | +| 4 | `LLMRunner.Execute`(`internal/runtime/llm.go:198`)准备阶段:模型路由 `ResolveChatModel`(`internal/runtime/model.go:54`,settings `models.chat.*` + 画像 ModelRoute,密钥经 SecretLookup);`@knowledge` 显式引用触发 RAG(llm.go:216 附近);system 段 `prompts.Service.ResolveSystem`(`internal/prompts/service.go:290`);上下文组装 `ctxasm.AssembleInput`/`FinishAssembly`(`internal/ctxasm/ctxasm.go:45`、`finish.go:97`,产出 `ContextObservation` 随 run.completed 下发);工具声明(编排型内置 + 注册表过滤,llm.go:267-315;计划模式过滤 `filterPlanDecls` llm.go:332) | | +| 5 | 进入内核:`l.runDirect(...)`(`internal/runtime/llm_direct.go:95`)把声明包成内核工具注册进 `agenttools.NewRegistry`、打开转录 journal(`/.agentx/transcript.jsonl`)、接线 Stop 钩子(交付物门禁 + 验证器)、调 `nruntime.Run(...)` | | +| 6 | **内核 ReAct 单轮循环** `nruntime.Run`(`deps/agent-native/runtime/loop.go:196`,`for turn < cfg.MaxTurns` 默认 12):① ctx/预算检查 → ② `prepareRequest`(loop.go:787:转录投影 + 窗口清理 + 记忆注入 + reminders)→ ③ `consume` 流式消费 LLM 事件(loop.go:492)→ ④ 持久化 assistant → ⑤ 有工具调用:`executeTools`(loop.go:595,可并行),每个 `runOne`(loop.go:709)走完整性校验 → `gateTool`(hooks PreToolUse,ask 决策交宿主 PermissionAsk)→ commitToolIntent(崩溃恢复配对)→ `cfg.Tools.Execute` → AfterTool,结果按 Role=tool 回填 → `continue` 下一轮 → ⑥ 无工具调用:`ContinueOnLength` 续写(loop.go:399)+ follow-up drain + Stop 钩子决定续跑(`stopDecision` loop.go:645;宿主侧做交付物门禁与验证,最多 3 次续跑)→ `settle` 结束;轮数耗尽报 max turns exceeded(loop.go:447) | | +| 7 | 工具执行回落宿主:编排型工具在 runner 内消化——`squad_orchestrate` → squadadapter.Orchestrate(case 在 `llm_direct.go:479`)、`delegate_task` → rc 端口 `SpawnBackground`/`SpawnAgent`(`llm_direct.go:493` 起)、`update_plan/update_todos` → `rc.UpdatePlan/UpdateTodos`、`run_workflow`、`submit_card/card_*`(各 case 依次在 `llm_direct.go:471` `runTool` 内);其余 → `rc.ExecuteTool`(`runmgr.go:740`):画像三清单 → policy.Decide(计划模式 deny / ask→HITL 确认卡)→ `registry.Execute`(`internal/tools/registry.go:94`)→ 结果截断转工件 | | +| 8 | 子代理 run(任务树):`spawnSubagent/startChild`(`runmgr.go:1348/1358`,startChild 做深度检查)建 `Kind=subagent` 子 run(ParentRunID 指父)→ 发 `agent.spawn`(`runmgr.go:1375`)→ `finishChild`(`runmgr.go:1401`)跑 `SubLLMRunner.Execute`(`internal/runtime/sub_loop.go:31`,同样调 `nruntime.Run`)→ 子转录独立落盘 → 结果截断 32000 字(`runmgr.go:1780` `capSubagentResult`)。后台子代理:`spawnBackground`(`runmgr.go:1475`,每会话上限 3);完成后 `deliverNotices`(`runmgr.go:1560`)在会话空闲时以 system-reminder 启动新父回合汇报(`startNoticeRun`,`runmgr.go:1616`) | | +| 9 | 终态收敛:`finishRun`(`runmgr.go:1087`)usage 汇总 → 终态消息树(tool_call/tool_result/thinking/card 部件)→ `SettleRun` 单事务落库 → submit_card 回填锚点 → 发 `run.completed`(含 ContextObservation)/`run.failed`。工件:`runCtx.WriteArtifact` → `store.CreateArtifact` → `artifact.created` 事件 → 文本进检索索引;工件作为 `PartArtifactRef` 挂进助手消息树 | | + +完成判定者 = 内核循环 + 宿主 Stop 钩子组合:模型不再调工具且无 follow-up → Stop 钩子 +(交付物门禁说谎检测、可选验证器)决定续跑或放行 → 可选用主模型 `composeFinal` 重写 +最终回复(`nruntime/compose.go`)→ `finishRun` 落终态。 + +--- + +## 4. LLM 调用与流式 + +- **不用第三方 SDK**,自研 HTTP 客户端 `deps/agent-native/runtime/llm/client.go`: + 默认 OpenAI 兼容 `POST {base}/chat/completions` + `stream:true`; + `Protocol=anthropic` 走原生 Messages(`anthropic.go`,带 cache_control 断点与签名思考块)。 +- **Provider 预设**:`llm/providers.go`(anthropic/openai/google/deepseek/minimax/ + openrouter/groq/azure)+ `models.json` 自定义;`ParseModelRef` 支持 `provider/model:thinking`。 +- **流式解析**:`llm/sse.go` `ParseSSE` → `emitParsed` 产出统一事件 + (text_delta / reasoning_delta / tool_call_delta / message_end)。 +- **模型配置链**:settings 覆盖链(defaults→system→user→project)+ 画像 ModelRoute + (`internal/runtime/model.go:30-62`);轻量档回落主档(model.go:84); + `models.chat.fallbacks` 故障切换(`llm/fallback.go`,在 runDirect 内接线); + 重试(内核 `WithRetry`)。 +- **事件到前端**:内核事件 → `directRun.onEvent`(`llm_direct.go:278`)转 `rc.Emit(...)` + → `runmgr.emit`(`runmgr.go:1217`)→ 事件缓冲合并(150ms/64 条,`internal/runmgr/events.go:42-43,83`) + → `store.AppendEvents`(事务内分配会话内单调 seq,`internal/store/messages.go:243`) + → `bus.Publish` → SSE `GET /api/events`(`server.go:275`,Last-Event-ID 重放,15s 心跳); + PG 多实例经 `internal/pgbus` NOTIFY/LISTEN 扇出。 + +--- + +## 5. 工具 / 技能 / 提示词体系 + +**工具系统** +- 契约:`modules/core/tools.go:16` `ToolDesc`(JSON Schema 参数、ReadOnly/Edits/Risk 权限声明)、 + `tools.go:33` `HTTPToolSpec` 无代码声明式 HTTP 工具。 +- 注册:`internal/tools/registry.go:50` `Register`(后注册覆盖)、`:112` `RegisterBuiltin` + (time_now、knowledge_search、write_artifact、quality_review、跨会话 peers、ask_user 等); + 另有 fs / exec / search / 声明式 HTTP / `run_tool_script`(需开关)各文件。 +- 执行策略在 runmgr:`ExecuteTool`(`runmgr.go:740`)deny→错误、计划模式 deny、 + confirm/ask→HITL 确认卡、执行后超长截断转工件。 +- 结果回填:内核侧作为 `Role=tool` 消息进转录;UI 侧由 onEvent 聚成 `PartToolCall`+ + `PartToolResult` 终态落库;下一轮由 ctxasm `renderAssistantTurns`(`internal/ctxasm/ctxasm.go:174` 附近) + 还原成 provider 原生配对消息。 + +**skills 包 = 内置技能文件**:go:embed `recipes/**`(`internal/skills/skills.go:23` 附近), +启动 `Seed`(skills.go:53)落 `/skills/`,带 manifest 哈希做升级/drift 保护; +供内核的 SKILL.md 加载器与 `load_skill` 渐进披露工具消费 +(`deps/agent-native/runtime/skills/skills.go`)。 + +**prompts 包 = 提示词模块库**:embed `library/*.md` + DB 用户覆盖链。 +`ResolveSystem`(`internal/prompts/service.go:290`):base/env 层按画像引用 → +task 层全员触发匹配(command/intent/attachment 三类触发)→ 正文点名的 `method.*` +辅助模块跟随注入 → 占位符填充(`{{date}}/{{model}}/{{skillsDir}}`)。 +另提供子代理 spec(tools/model/maxTurns,`service.go:81` 附近;**禁止子代理持有派发类工具**)、 +AgentPrompt、工具描述覆盖。 + +**ctxasm 包 = 上下文组装**:`AssembleInput`(`internal/ctxasm/ctxasm.go:45`)= +sys 段 → 历史窗口(默认 40 条,超出注入抽取式摘要)→ 卡片/工件正文回填 +(防"下一轮失忆")→ 当前轮文本 + 附件解析全文(默认 48000 字符截断)+ knowledge 块; +`FinishAssembly`(`finish.go:97`)追加条件注入;产出 Report(token 估算、裁剪记录、 +prefixHash)作为可观测 `ContextObservation`。记忆注入在内核按请求完成(`loop.go` prepareRequest 段)。 + +--- + +## 6. HITL(人工介入) + +- 契约:`modules/core/hitl.go:33-36` 四种卡(choice/input/confirm/form)。 +- 挂起:`runCtx.AskHITL`(`runmgr.go:449`)存卡 → run 置 waiting_hitl → 发 + `hitl.request` → 收件箱投影 → `waitHITL` 阻塞等恢复(支持超时)。 +- 恢复:`POST /api/hitl/{id}/resolve` → `runmgr.ResolveHITL`(`runmgr.go:1261`) + 三分支:选项回填恢复;自由输入命中其他意图 → 取消挂起 run 并转轨;否则作澄清回答回填。 +- 触发时机:a) 工具确认(画像 confirm 清单 / policy ask,`runmgr.go:757` 附近与内核 + `PermissionAsk` 端口);b) 工作流缺槽位提问(`internal/wfcompile/run.go` 槽位补齐段)、 + 显式 hitl 步骤(`internal/wfcompile/host.go:138` `doHitl`)、动态计划执行前确认 + (`RunOptions.RequireConfirm`,`internal/wfcompile/run.go:34`);c) `ask_user` 工具。 + +--- + +## 7. workflow 体系与"规划器" + +- `internal/workflow/registry.go`:工作流**定义注册表**(只依赖 core)。 + `Registry.Set` 整体替换快照、`ByIntent` 按意图查定义、`Launch` 产启动副本。 +- **编译执行器在 `internal/wfcompile/`**(Q3 WP1): + - `run.go:39` `RunCompiled`:缺槽位逐个 HITL input 卡收齐 → `compiler.go:76` `Compile` + 编译 WorkflowDef → **装配到小队引擎(squad)步进执行**(挂起 → HITL 卡片 → Respond; + 执行态 → Resume)→ critic 终判 → outcome 汇总(run.go 文件头注释即此流程)。 + - `RunOptions`(run.go:27):`DynamicLimits`(模型生成上限:≤8 步、禁 script)、 + `RequireConfirm`(模型生成工作流执行前强制 HITL 确认;注册的声明式工作流免确认)、 + `ModelScript`(脚本步为模型提供,执行前单独确认)。 +- **"先规划任务列表再执行"的 planner 真实存在**:`internal/runtime/workflow_compiled.go:91` + `planCompiledWorkflow` —— `rc.SpawnAgent(workflowPlannerID, "规划", …)` 派**规划子代理** + 输出 WorkflowDef JSON("只输出 JSON,不要完成目标本身")→ `wfcompile.ParseWorkflowDef` + 解析校验 → `runLaunch` 以 `DynamicLimits:true, RequireConfirm:true` 执行(用户 HITL + 确认计划后步进)。另有 `registeredWorkflowLaunch`(:117,注册定义 + 快照脚本正文)、 + `modelScriptLaunch`(:138,模型脚本,单独确认)。入口 Runner 是 + `CompiledWorkflowRunner`(workflow_compiled.go:30,`Execute` :39)。 + +--- + +## 8. deps/agent-native 内核的其他部件 + +- `runtime/` 是**进程内自研内核**(不派子进程):`loop.go` 循环、`llm/` 客户端、 + `tools/` 内核注册表(deferred 延迟暴露 + tool_search)、`session/`(JSONL 转录树, + 可 fork/clone)、`session_agent.go`(headless AgentSession:steer/follow_up/abort/换模型)、 + `hooks/`、`memory/`、`skills/`、`promptx/`、`mcp/`、`checkpoint/`、`subagent/`、 + `gates/`(验收+守卫)、`codeorch/`(Python 依序调工具的代码编排)、`policy/`。 +- `rpc/` 是本地 JSONL RPC(一行一命令,双向 pipe),把内核暴露给子进程/外部宿主; + **产品后端主路径不走 rpc**,直接进程内调 `nruntime.Run`。 +- `adapters/native/backend.go` 把内核包成 `exec.Backend`(`exec/backend.go` 接口), + 供小队成员执行使用(见[小队与子 Agent 启动](squad-and-subagents.md)、 + [小队工作机制](squad-mechanics.md));`adapters/pitransport/` 是远程 pi runner 的 + HTTP/NDJSON 传输适配(产品未用为主)。 + +--- + +## 9. 关键文件清单 + +| 职责 | 文件 | +|---|---| +| Run/消息/计划/工件契约 | `modules/core/model.go`、`events.go`、`hitl.go`、`tools.go` | +| Run 生命周期枢纽 | `modules/backend/internal/runmgr/runmgr.go` | +| 事件缓冲与 seq | `modules/backend/internal/runmgr/events.go` | +| Runner 端口与四实现 | `internal/runtime/runner.go`、`llm.go`、`sub_loop.go`、`workflow_compiled.go`、`squad_runner.go` | +| 内核桥接(工具/事件/Stop 钩子) | `internal/runtime/llm_direct.go`(runDirect :95、onEvent :278、runTool :471) | +| 模型路由 | `internal/runtime/model.go` | +| 工作流编译执行 | `internal/wfcompile/run.go`(RunCompiled :39)、`compiler.go`(Compile :76)、`host.go`(doHitl :138) | +| 工作流入口与 planner | `internal/runtime/workflow_compiled.go`(CompiledWorkflowRunner :30、planCompiledWorkflow :91) | +| 工作流定义注册表 | `internal/workflow/registry.go` | +| 内核 ReAct 循环 | `deps/agent-native/runtime/loop.go` | +| LLM 客户端/协议/预设 | `deps/agent-native/runtime/llm/client.go`、`providers.go`、`sse.go`、`anthropic.go` | +| 上下文组装 | `internal/ctxasm/ctxasm.go`、`finish.go` | +| 提示词库 | `internal/prompts/service.go` | +| 技能种子 | `internal/skills/skills.go` | +| 工具注册表 | `internal/tools/registry.go` | +| run/事件/工件落库 | `internal/store/messages.go`、`recovery.go` | +| HTTP 入口与 SSE | `internal/server/server.go` | -- 2.54.0 From 4ce8155161ebad37875562f9d6b5f33d7973495e Mon Sep 17 00:00:00 2001 From: weichen Date: Thu, 8 Oct 2026 09:10:20 +0000 Subject: [PATCH 2/2] Expand guides with worked examples for intents, runs, and squads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: 补充实例让导览可对照实操:意图打分/填槽/多意图裁决给出 intentctl explain 实测轨迹(含样例意图、歧义、优先级 tie-break、 能力目录),任务执行给出工具清单与典型回合走查(含 ClaimGate 三类拦截),小队给出 agent-squad demo 全流程实测与真实工作区 目录树/TASK.md 实拍;并修正事实:默认意图目录为空、插件 overlay 与 registry loader 两种文件形态不同。 Evidence: intentctl explain/catalog 与 agent-squad demo 为 2026-10-08 本地实测输出(命令见各节);锚点仍为 Dev R0 218fc4b1;git diff --check 通过;五篇互链全部可解析。SRC=SELF。 Impact: 仅文档增补(02/03/04/05 四篇),无代码影响。 Refs: Dev 任务分支 feature/architecture-guides。 --- docs/guides/intent-recognition.md | 115 +++++++++++++++++++++ docs/guides/squad-and-subagents.md | 6 +- docs/guides/squad-mechanics.md | 84 +++++++++++++++ docs/guides/task-planning-and-execution.md | 47 +++++++++ 4 files changed, 250 insertions(+), 2 deletions(-) diff --git a/docs/guides/intent-recognition.md b/docs/guides/intent-recognition.md index 11ff777..7c59938 100644 --- a/docs/guides/intent-recognition.md +++ b/docs/guides/intent-recognition.md @@ -83,6 +83,121 @@ clarify / chitchat / handoff`;规则漏斗只产出 start/set_slot/clarify(` (`intent/catalog.go:36,68`)把启用意图渲染为 Markdown,供小队规划者/Leader 提示词注入 (`deps/agent-squad/adapters/intent/catalog.go` 消费)。 +## 4.5 内置与默认意图:默认是空目录 + +**系统默认不内置任何意图**:backend 组合根 `intentgate.New()`(`app.go:309`)起步是空注册表, +`g.Match` 在空闸门下直接透传(未启用 = 行为回退,所有消息走普通回合)。意图只能经两条 +overlay 路径进入,且**两种路径的文件形态不同**: + +| 路径 | 文件形态 | 装载方 | +|---|---|---| +| 插件 overlay(产品路径) | **裸数组** `[ {…IntentSpec…}, … ]` | `plugins/host.go:216-220` 按元素透传 → `gate.Rebuild`(gate.go:104)逐个 unmarshal 成 IntentSpec | +| registry/loader(库路径:intentctl、BuildFS) | 包装对象 `{"intents":[…], "disable":[…]}` | `intent/loader.go:21` `overlayDoc`,严格 JSON(未知字段报错) | + +仓库自带两份样例意图(即"默认能看到的意图"上限——装载对应插件/配置后生效): + +- `plugins/example/intents/demo.json`(插件形态):`demo.show`(命中"示例/演示",无槽位)、 + `demo.brief`(命中"简报",必填槽位 `topic`,抽取正则 `主题[::]\s*(\S+)`,缺槽问"主题是什么?")。 +- `deps/agent-intent/examples/specs/demo.json`(loader 形态):`demo.run.status_query` + (命中"进度/状态"、排除"配置",槽位 `run_id`,base=0.8/threshold=0.7,handler=`taskrun.query_status`)、 + `demo.run.restart`(需同时含"重启"+"任务",`requires_confirm=true`,risk=medium)。 + +--- + +## 4.6 实测:一句话怎么被打分、填槽、命中 + +以下全部为 `intentctl explain` 在样例配置上的**真实输出**(2026-10-08 实测;例 5/6 的配置目录 += 两份样例意图合并)。打分公式:`score = base + (正则命中 ? pattern_bonus : 0)`(封顶 1.0), +前两名分差 < `margin`(默认 0.1,`intent/registry.go:55`)判 ambiguous。 + +**例 1|命中 + 抽到槽位**(输入"查一下任务 run_42 的进度"): + +``` +分支:matched +Top:demo.run.status_query 置信度=0.8000 风险=low 确认=false handler=taskrun.query_status + 槽位 run_id="run" +候选(1): + 0.8000 demo.run.status_query terms=进度 +裁决轨迹: + margin=0.1000 + 守卫:未评估 + 规则 命中 demo.run.status_query 0.8000 进度 + 规则 排除 demo.run.restart no_positive_match +``` + +要点:`terms=进度` 命中词表加 0 分(base 已含);`restart` 因 `includes_all:["任务"]` 缺"重启" +被排除(`missing_includes_all`)。槽位 `run_id="run"` 是真实结果——槽位正则 +`(?:run|任务)[:# ]*([A-Za-z0-9-]+)` 的字符类不含下划线,捕获在 `run` 后被 `_` 截断: +**规则识别完全确定性,槽位 pattern 的质量决定抽取质量**(写 `[A-Za-z0-9_-]+` 即可取到 `run_42`)。 + +**例 2|命中但缺必填槽位**(输入"写一份简报",`demo.brief` 装载时): + +``` +分支:matched +Top:demo.brief 置信度=0.7000 + 缺失必填槽位:topic +裁决轨迹: + 规则 命中 demo.brief 0.7000 简报 + 规则 排除 demo.run.restart missing_includes_all:任务 + 规则 排除 demo.run.status_query no_positive_match + 规则 排除 demo.show no_positive_match +``` + +`Missing=[topic]` 连同 spec 的 `question:"主题是什么?"` 一起随 route 进入工作流 run, +由 HITL 输入卡向用户澄清(见调用链第 10 步)。 + +**例 3|槽位填上**(输入"主题:核能 写一份简报"):同一意图 matched, +`槽位 topic="核能"`,Missing 为空——正则 `主题[::]\s*(\S+)` 命中,直接启动。 + +**例 4|什么都不像**(输入"随便聊聊今天的天气"): + +``` +分支:fallback +裁决轨迹: + 规则 排除 demo.run.restart missing_includes_all:任务 + 规则 排除 demo.run.status_query no_positive_match +``` + +无命中 → fallback;若装载了语义慢通道(`runtime.intentSemantic`,默认关)会先尝试 embedding +重排,仍不达阈才 fallback。gate 把 fallback 折叠后 `route.Decide` 判 `KindTurn` → 普通对话回合。 + +**例 5|能力目录实测**(`intentctl catalog`,这段 Markdown 就是注入小队规划者/Leader +提示词的内容,`squad/engine.go:44` 注释明示用途): + +``` +# 系统能力目录(意图快照 revision=1) + +以下能力可直接调用,无需派智能体执行。子任务命中其一时,输出携带 intent_id 的步骤并填齐必填槽位; +必填槽位信息不足时不要编造,改为向用户澄清(优先使用各槽位的提问建议)。 + +## demo.brief — 写一份简报。必填槽位 topic,没写就问「主题是什么?」。 +- 风险:low;执行前需人工确认:否 +- 槽位 topic(必填,topic) + - 提问建议:主题是什么? +``` + +--- + +## 4.7 一句话里有多个意图怎么办 + +意图层的契约是**一条输入只产出一个 Decision**(top-1),不做多意图拆分。多个候选的 +裁决顺序固定为: + +1. **分差裁决**:唯一过阈,或第一名领先 ≥ margin → 直接 matched。 +2. **同分且 priority 不同 → 高优先级直接胜出**(I5,不标歧)。实测("帮我生成一份报告", + 两个意图 base 都是 0.7、词表都命中"报告"):template 设 `priority:10` 后 + `分支:matched,Top:demo.report.template 置信度=0.7000`。 +3. **分差 < margin(含同分同优先级)→ ambiguous**,先走可选语义重排 `semanticRerank` + (`recognizer.go:292`,默认关)→ 再走 Classifier L2 重打分(backend 未接线)→ 都没有则 + **保持 ambiguous**。实测同配置去掉 priority 后: + `分支:ambiguous,候选(2):0.7000 demo.report.generate / 0.7000 demo.report.template`。 +4. ambiguous 经 gate 折叠 → `KindTurn` → **交给普通 LLM 回合**:模型在一次 ReAct 循环里 + 自行处理多个请求(这也是多数"一句话多个事"的实际归宿——意图层只接管高确定性的 + 命令/工作流类请求)。 +5. 多轮场景:Stage 1 挂起预期(`ExpectationProvider`)让澄清中的输入优先回到挂起意图; + HITL 自由输入命中其他意图时会**取消挂起 run 并转轨**(`runmgr.go:1261` `ResolveHITL`), + 相当于允许用户中途改道。 + --- ## 5. 端到端调用链:用户输入一条消息后发生了什么 diff --git a/docs/guides/squad-and-subagents.md b/docs/guides/squad-and-subagents.md index 3196710..61cf7b6 100644 --- a/docs/guides/squad-and-subagents.md +++ b/docs/guides/squad-and-subagents.md @@ -84,8 +84,10 @@ tools/model/maxTurns 约束,**禁止子代理再持有派发类工具**,`int ### 一条"不可达"的路径(设计预留) 意图识别绑定 `blueprint` 动作可路由到 `KindSquad`(`internal/route/route.go:96-97`), -但当前 `server.go:664` 明确注释 `SquadReady 保持 false`(per-run entry 覆盖未就位), -**该路径暂不可达**——小队的意图目录注入另走 `deps/agent-squad/adapters/intent/catalog.go`。 +但当前**双重不可达**:`server.go:664` 明确注释 `SquadReady 保持 false`(per-run entry +覆盖未就位);且系统默认意图目录为空(见[意图识别](intent-recognition.md) 4.5 节), +不装插件就没有意图可命中 blueprint。小队的实际启动走上面 A/B/C 三条路径;意图目录 +对小队的现实作用是注入规划者/Leader 提示词的能力目录(`deps/agent-squad/adapters/intent/catalog.go`)。 --- diff --git a/docs/guides/squad-mechanics.md b/docs/guides/squad-mechanics.md index dbb33c0..b0acd4d 100644 --- a/docs/guides/squad-mechanics.md +++ b/docs/guides/squad-mechanics.md @@ -91,6 +91,90 @@ blackboard.json / pending.json / runstate.json / usage.json` --- +## 4.5 实测:离线小队演示(真实输出) + +`deps/agent-squad` 自带离线演示(`cmd/agent-squad demo -keep`,stub 脚本成员 + 规划脚本 + +自动应答 HITL,无需 LLM/网络),以下为其 2026-10-08 实测输出摘录。**引擎机制与产品完全 +同一套**,差异只有三处:成员后端是脚本(产品是 agent-native 内核)、规划器是 MockPlanner +(产品默认 RulePlanner)、HITL 自动应答(产品走 HITL 卡片)。 + +``` +═══ agent-squad 离线演示(stub 智能体 + mock 规划器 + HITL)═══ + +[1] 注册角色(手动注册,backend=demo 脚本智能体) + + researcher 调研专员 + writer 撰写专员 + reviewer 审核专员 + +[2] 发起运行:调研便携式超声诊断仪并产出报告 + ✔ step_01_research researcher 市场与技术调研,回填事实与缺口 + step_02_draft writer 汇总调研产出,撰写报告初稿 + step_03_review reviewer 一致性终审并产出终版 + +[3] ⏸ HITL 挂起(gap):发现 1 个阻塞缺口,需要人工补充信息后才能继续: + - [gap_1] 目标年产量未提供,影响成本测算(owner=agent) + → 自动回应:choice="resolved" answer="年产量 10 万套" + ✔ step_01_research ✔ step_02_draft ✔ step_03_review + +[4] 运行终态:completed + 步骤 3 个(完成 3/跳过 0/失败 0)。质检得分 100。 + 交付物: /runs/run_20261008T090457.882598947/output.md + 黑板字段: {"annual_volume":"100000","budget":"15万","device_class":"二类", + "review":"passed","target_market":"中国"} +``` + +对应真实落盘的工作区(`runs//`)——与第 4 节调用链逐项对应: + +```text +runs/run_20261008T090457.882598947/ +├── context/goal.md, context/index.json # 9. 目标与材料快照 +├── tasks/01_researcher/ # 9. 每步一个子工作区 +│ ├── TASK.md # 步骤任务书(角色、期望产出、资源权威等级、黑板快照、产出要求) +│ ├── AGENTS.md # 角色指令 +│ ├── context/ # 快照进来的目标/材料 +│ ├── notes.md fields.json gaps.json # 成员产出(黑板来源) +│ └── execution.log +├── tasks/02_writer/{TASK.md,AGENTS.md,context/,output.md,...} +├── tasks/03_reviewer/{TASK.md,AGENTS.md,context/,output.md,fields.json,...} +├── blackboard.json # 11. 黑板(字段在步骤间累积) +├── workflow.json squad.json runstate.json # 7. 拆解结果与运行态(磁盘即权威) +├── checkpoints/{001,002,latest}.json # 11. superstep 检查点 +├── output.md manifest.json # 14. 聚合交付物 + sha256 清单 +└── .lock # 6. acquire 的 flock 文件锁 +``` + +`tasks/01_researcher/TASK.md` 实拍(节选): + +```markdown +# 步骤任务:市场与技术调研,回填事实与缺口 +- 角色:调研专员(researcher) +- 期望产出:[notes.md fields.json gaps.json] +## 任务上下文资源与权威等级(已快照到本目录,只引用本地路径) +| 资源路径 | 权威等级 | 用途说明 | +| `context/goal.md` | **authoritative** | 运行目标 | 不可推翻 | +## 黑板快照(共享状态) +## 产出要求 +1. 按期望产出文件名落盘到本子目录; +2. 结构化事实回填 `fields.json`(字符串键值); +3. 发现信息缺口且无法假设时写 `gaps.json`([{"item":"…","blocking":true}])。 +``` + +产品的默认拆解(RulePlanner 四阶段骨架,`squad/planner_rule.go:79` 起)与演示计划同构, +真实代码里的步骤与交付约定: + +```go +// 通用四阶段骨架:梳理 → 专项 → 成文 → 终审 +{"step_01_organizer", "梳理目标背景与输入材料,产出事实与缺口", "organizer"}, +{"step_02_specialist", "围绕目标开展专业分析与推演", "specialist"}, +{"step_03_writer", "汇总各角色产出,撰写交付物初稿", "writer"}, +{"step_04_leader", "一致性终审与交付把关", "leader"}, +// 非末步 targets = ["notes.md","fields.json"](事实经黑板传给后步);末步 = ["output.md"] +``` + +注册表缺角色时 `Registry.Derive` 现场派生(名为"organizer 专员"等)并置 +`NeedsConfirmation` → `awaiting_confirmation` 挂起等用户确认编制。意图目录注入规划器/ +Leader 的形态见[意图识别](intent-recognition.md) 4.6 节例 5 的 catalog 实测输出。 + +--- + ## 5. 小队如何"协作" - **黑板(Blackboard)**是协作中枢:Facts(已确认事实)/Gaps(信息缺口)/ diff --git a/docs/guides/task-planning-and-execution.md b/docs/guides/task-planning-and-execution.md index 0ae08ef..785ccfc 100644 --- a/docs/guides/task-planning-and-execution.md +++ b/docs/guides/task-planning-and-execution.md @@ -125,6 +125,53 @@ prefixHash)作为可观测 `ContextObservation`。记忆注入在内核按请 --- +## 5.5 主对话模型手上有哪些工具 + +模型每轮请求能看到的工具分三类(全部在 `LLMRunner.Execute` 声明阶段组装,`llm.go:267-315`): + +| 类别 | 工具名(实测常量/注册名) | 来源 | +|---|---|---| +| **编排型**(在 runner 内消化,不走通用执行器) | `update_plan` / `update_todos`(维护右栏计划/待办)、`delegate_task`(派子代理)、`squad_orchestrate`(发起小队)、`run_workflow`(发起工作流)、`review_findings`、`submit_card`、`card_begin`/`card_append`/`card_commit`(流式产卡三段式) | `internal/runtime/llm.go:56-70` 常量 | +| **内置产品工具** | `time_now`、`knowledge_search`(项目/插件知识全文检索)、`write_artifact`(写工件)、`quality_review`(交付物打分)、`ask_user`(主动提问=HITL)、跨会话 `peers` 系列 | `internal/tools/registry.go:112` `RegisterBuiltin` | +| **内核工作区工具**(经 hostbridge 注入内核) | `read_file` / `write_file` / `edit_file` / `multi_edit` / `list_dir` / `grep` / `glob` / `load_skill` / `run_python` / `run_command` / `web_search` / `tavily_search` / `fetch_url` / `delegate_task` | `internal/runtime/hostbridge.go:15-21` `kernelToolNames`;`web_search`/`tavily_search` 仅在配置了搜索密钥时注册 | + +实际可见集 = 以上与**画像三清单**(allow/deny/confirm,`core/profile.go`)过滤后的交集; +计划模式下 `filterPlanDecls`(llm.go:332)会再收窄。 + +--- + +## 5.6 实例:一个典型回合的机制走查 + +> 走查基于上文全部代码锚点;模型输出为示意,机制、工具名与顺序均为代码实测。 + +用户输入:"把我会话里刚上传的季度数据整理成一份摘要文档,并告诉我三个关键结论。" + +1. **进入回合**:`postMessage` → 意图 fallback(没有装匹配意图)→ `route.Decide` 判 + `KindTurn` → 抢会话槽位 → `startClaimedRun` 建 `Run{pending}` → 选 `LLMRunner` → + `go execute()`(`runmgr.go:1023, 918`)。 +2. **上下文组装**:`ctxasm.AssembleInput`(ctxasm.go:45)——system 段(base/env/task 提示词 + 模块,`prompts.ResolveSystem`)→ 历史窗口(默认 40 条)→ 当前轮文本 + **附件解析全文** + (ingest 已把 xlsx/csv 解成 ParsedDocument,默认 48000 字符截断)→ knowledge 块。 +3. **第 1 轮 LLM**:模型看到数据后先调 `update_todos`(拆 3 个待办)+ `read_file`(读附件 + 解析物)。两个工具经内核 `runTool` 回落宿主执行,结果以 `Role=tool` 消息回填转录 + (loop.go `executeTools` 段),UI 侧聚成工具过程部件。 +4. **第 2 轮 LLM**:模型调 `write_artifact`(把摘要写成工件)。宿主执行 → `artifact.created` + 事件 → 工件登记进会话。 +5. **第 3 轮 LLM**:模型输出三个结论的纯文本(无工具调用)→ 内核判定无工具调用 → + follow-up drain → **Stop 钩子**:宿主做交付物门禁(`deps/agent-native/runtime/gates/gates.go:52` + `ClaimGate`)——它抓三类"说谎":① 回复正文出现工具调用标记但本轮没有真实调用; + ② 引用了不存在的工件 ID;③ **声称"已生成/已登记文件"但这轮没有产出对应工件**。 + 本例模型确实产出了工件,门禁放行;若模型只写了结论却声称"文件已生成", + 门禁拒绝结算并附原因让模型续跑(最多 3 次续跑,`loop.go` stopDecision + 宿主接线)。 +6. **收口**:可选用主模型 `composeFinal` 重写最终回复 → `finishRun`(runmgr.go:1087)→ + `SettleRun` 单事务落 run 终态与助手消息树 → 发 `run.completed`(带 ContextObservation)。 + +子代理变体:若第 2 轮模型改调 `delegate_task {agent:"researcher", task:"分析季度数据"}`, +则走 `llm_direct.go:493` → `runmgr.go:1348` 派生 `Kind=subagent` 子 run(任务树 + `agent.spawn` +事件)→ `SubLLMRunner` 用同一内核循环独立跑完 → 结果回填父轮。 + +--- + ## 6. HITL(人工介入) - 契约:`modules/core/hitl.go:33-36` 四种卡(choice/input/confirm/form)。 -- 2.54.0