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。
20 KiB
Agent 的任务规划与执行:架构与调用链
基准:Agent-System-Dev 组合
218fc4b1ba00b0a03b5bb79f292b3eba6c987a58(R0,2026-10-08 锚点核验)。 行号为该提交下的函数入口位置(个别内部区段为约略范围),随代码演进漂移;文件与函数名是稳定锚点。internal/指modules/backend/internal/;core/指modules/core/;nruntime/指deps/agent-native/runtime/。
1. 一句话结论
系统有两套并存的规划机制:
- 默认主线 = LLM 自主规划(ReAct 循环):没有预置 DAG。普通消息由 backend
LLMRunner驱动,进入deps/agent-native自研内核的单轮循环 (组装上下文 → 调 LLM → 解析工具调用 → 执行工具 → 回填 → 直到模型不再调工具), 模型可用update_plan/update_todos工具自主维护任务列表(右栏 Plan/Todos 面板)。 - 可选结构化路径 = WorkflowDef(数据化步骤列表):可由意图绑定启动,也可由模型
调用
run_workflow goal=...触发"规划子代理生成步骤 → 用户 HITL 确认 → 编译执行" (internal/runtime/workflow_compiled.go:91planCompiledWorkflow)。工作流的 编译与步进执行在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 | interruptedRunPhase(model.go:178):thinking → acting → responding → finalizing → doneRunKind(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(<sandbox>/.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.goParseSSE→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→ SSEGET /api/events(server.go:275,Last-Event-ID 重放,15s 心跳); PG 多实例经internal/pgbusNOTIFY/LISTEN 扇出。
5. 工具 / 技能 / 提示词体系
工具系统
- 契约:
modules/core/tools.go:16ToolDesc(JSON Schema 参数、ReadOnly/Edits/Risk 权限声明)、tools.go:33HTTPToolSpec无代码声明式 HTTP 工具。 - 注册:
internal/tools/registry.go:50Register(后注册覆盖)、:112RegisterBuiltin(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终态落库;下一轮由 ctxasmrenderAssistantTurns(internal/ctxasm/ctxasm.go:174附近) 还原成 provider 原生配对消息。
skills 包 = 内置技能文件:go:embed recipes/**(internal/skills/skills.go:23 附近),
启动 Seed(skills.go:53)落 <data>/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 实例:一个典型回合的机制走查
走查基于上文全部代码锚点;模型输出为示意,机制、工具名与顺序均为代码实测。
用户输入:"把我会话里刚上传的季度数据整理成一份摘要文档,并告诉我三个关键结论。"
- 进入回合:
postMessage→ 意图 fallback(没有装匹配意图)→route.Decide判KindTurn→ 抢会话槽位 →startClaimedRun建Run{pending}→ 选LLMRunner→go execute()(runmgr.go:1023, 918)。 - 上下文组装:
ctxasm.AssembleInput(ctxasm.go:45)——system 段(base/env/task 提示词 模块,prompts.ResolveSystem)→ 历史窗口(默认 40 条)→ 当前轮文本 + 附件解析全文 (ingest 已把 xlsx/csv 解成 ParsedDocument,默认 48000 字符截断)→ knowledge 块。 - 第 1 轮 LLM:模型看到数据后先调
update_todos(拆 3 个待办)+read_file(读附件 解析物)。两个工具经内核runTool回落宿主执行,结果以Role=tool消息回填转录 (loop.goexecuteTools段),UI 侧聚成工具过程部件。 - 第 2 轮 LLM:模型调
write_artifact(把摘要写成工件)。宿主执行 →artifact.created事件 → 工件登记进会话。 - 第 3 轮 LLM:模型输出三个结论的纯文本(无工具调用)→ 内核判定无工具调用 →
follow-up drain → Stop 钩子:宿主做交付物门禁(
deps/agent-native/runtime/gates/gates.go:52ClaimGate)——它抓三类"说谎":① 回复正文出现工具调用标记但本轮没有真实调用; ② 引用了不存在的工件 ID;③ 声称"已生成/已登记文件"但这轮没有产出对应工件。 本例模型确实产出了工件,门禁放行;若模型只写了结论却声称"文件已生成", 门禁拒绝结算并附原因让模型续跑(最多 3 次续跑,loop.gostopDecision + 宿主接线)。 - 收口:可选用主模型
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:138doHitl)、动态计划执行前确认 (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:39RunCompiled:缺槽位逐个 HITL input 卡收齐 →compiler.go:76Compile编译 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:91planCompiledWorkflow——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 启动、 小队工作机制);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 |