# 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 段)。 --- ## 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)。 - 挂起:`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` |