35 KiB
NCurator-Desktop AI 完整样例
本文不描述理想方案,而是按当前代码实现,模拟两条真实可发生的 run:
- 样例 A:复杂路径,覆盖
director_context_human、director_plan、director_plan_exec、director_synthesis_human - 样例 B:默认路径,覆盖
director_direct_human_exec
本文里的字段名、节点名、交付模式、状态转移与代码保持一致;示例中的模型结构化输出、工具结果和 markdown 内容是“符合当前契约的一组代表性样本”,不是固定字面值。
1. 先看三个总原则
1.1 真实运行入口
- run 入口:
src/main/services/ai/AiRunService.ts directorgraph:src/main/services/ai/director/graph/directorGraph.tshumangraph:src/main/services/ai/human/graph/humanGraph.ts
1.2 真正跨层传递的不是 HumanState
director 不直接读写 HumanState。跨层只看三类数据:
contextEvidenceplan[].deliveryMarkdown / plan[].artifactsHumanTaskOutput
这意味着每次 human 子任务都是一个新的局部闭环,内部状态不会直接继承上一个 human 的 HumanState。
1.3 deliveryMode 只改变 mouth 怎么交付
context_note:给director_context_humantask_handoff:给director_plan_execuser_answer:给director_direct_human_exec和director_synthesis_human
它不改变 brain / learning / hand 的职责,只改变最终 mouth 的输出形态,以及部分事件是否带 internal=true。
2. 样例 A:复杂请求完整跑一遍
2.1 用户请求
假设用户这轮输入是:
请把 Alpha PRD 的最新 docx 按上周评审意见和当前发布模板修好,统一术语、补充发布日期说明,生成预览并保存到
docs/output/PRD-Alpha-reviewed.docx,最后告诉我改了什么和还需要人工确认什么。
运行前提:
- 已绑定 workspace
- 已选本地资料范围,包含 Alpha PRD 所在目录和两份知识文档
- 会话里没有足够明确的“最新 docx 是哪一份”“上周评审意见具体指什么”
为了便于阅读,下文把 selectedScopeSummary 近似理解成:
workspace=/project-alpha
selected scopes:
- workspace directory: docs/prd
- kb document: 品牌术语规范
- kb document: 发布说明模板
2.2 初始 AiRunState
AiRunService.run() 会先创建初始 run state:
{
runId: 'run-a',
sessionId: 'session-a',
status: 'running',
input: '请把 Alpha PRD 的最新 docx 按上周评审意见和当前发布模板修好……',
directorStage: 'director_intent',
contextRounds: 0,
contextEvidence: [],
plan: [],
dispatchCursor: 0,
finalArtifacts: []
}
这里还没有任何 human 状态。director 先决定这轮是直接做、先补上下文,还是拆计划。
2.3 第 1 步:director_intent 首次路由
director_intent 真实会读取:
state.inputstate.contextRoundsstate.contextEvidencectx.loadHistory()返回的会话摘要和最近对话ctx.selectedScopeSummary
在这个样例里,模型可能给出这样的结构化结果:
{
"mode": "need_context",
"reason": "用户说的是“最新 docx”和“上周评审意见”,但当前输入里没有唯一文件定位和可执行规则摘要,先补上下文更稳。"
}
于是 director_intent 返回 patch:
{
contextObjective: '先确认目标 docx、上周评审意见来源,以及可直接复用的规则证据。',
directorStage: 'director_context_human'
}
此时没有 final answer,也没有 plan。
2.4 第 2 步:director_context_human
director_context_human 会做三件事:
- 生成
taskId = context-1 - 用
buildContextHumanObjective()组织新的userInput - 派发一个新的
human
派发参数的关键点:
{
taskId: 'context-1',
deliveryMode: 'context_note',
internalDelivery: true
}
这里的 internalDelivery=true 很关键,因为这不是给最终用户看的正式答复,而是给 director 回填上下文证据。
2.5 context-1 这个 human 是怎么跑的
每个 human 都从全新的 HumanState 开始:
{
task: {
taskId: 'context-1',
userInput: '# 上下文补充任务 ...'
},
runtime: {
stage: 'brain',
status: 'running'
},
brainMemory: {
inside: {
facts: [],
evidences: [],
openQuestions: []
},
outside: {
skillCatalog: [/* 全量 skill 摘要,初始化即存在 */],
learnedSkillIds: [],
brainDecisions: []
}
},
runtimeArtifacts: [],
trace: {
steps: []
}
}
2.5.1 brain 第一次决策,先触发一次 watchHint
为了展示真实的纠偏逻辑,先模拟一个当前代码允许发生的场景:
brain想先写一个 fact- 但这个 fact 引用了尚未存在的
evidenceId
例如模型输出:
{
"memoryOps": [
{
"op": "add",
"field": "facts",
"value": {
"id": "fact-docx-target",
"summary": "目标文档是 Alpha PRD 的最新 docx。",
"evidenceIds": ["ev-docx-path"]
}
}
],
"decision": {
"summary": "需要先学习本地定位和文档读取能力。",
"reason": "当前还没有证据支持目标文档定位。",
"expectedOutcome": "拿到可执行的文件路径和规则来源。"
},
"nextAction": {
"action": "learning",
"wantLearnSkillIds": ["workspace-cli", "kb-document-discovery", "docx-read"]
}
}
这份输出先经过 reduceBrainMemory()。因为 fact 依赖的 ev-docx-path 还不存在,memoryReducer 会返回:
{
inside: /* 回退到原 inside */,
watchHint: 'memoryReducer 检测到非法记忆编辑,已忽略对应操作,请重新判断下一步。'
}
于是这一次 brain 的真实结果是:
lastBrainDecision仍会被写入brainDecisions追加一条历史trace.steps追加一条brain_decisionwatchHint被写入runtime.stage不去learning,而是继续留在brain
这就是 applyBrainDecisionTransition() 里的真实逻辑:一旦存在 watchHint,下一跳强制回到 brain。
2.5.2 brain 第二次决策,进入 learning
第二轮 brain 读到 watchHint 后,会更谨慎,只先学能力,不抢先写 fact:
{
"memoryOps": [],
"decision": {
"summary": "先学习定位文件、文档读取和知识文档发现能力。",
"reason": "当前目标是补上下文,不是直接求解。",
"expectedOutcome": "确认目标 docx、评审意见来源和模板规则来源。"
},
"nextAction": {
"action": "learning",
"wantLearnSkillIds": ["workspace-cli", "kb-document-discovery", "docx-read"]
}
}
learning 节点会:
- 校验 skill 是否存在
- 校验是否重复学习
- 把这些 skill 对应的 tools 装入 runtime cache
- 回写
learnedSkillIds - 追加
learn_skilltrace - 返回
brain
这一步之后,learnedSkillIds 变成:
['workspace-cli', 'kb-document-discovery', 'docx-read']
但注意:
skillCatalog本来就有- 真正决定“当前可调用哪些工具”的,是
learnedSkillIds + runtime cache
2.5.3 brain 第三次决策,进入 hand 做第一批工具调用
这次 brain 可以发出一个合法的只读并行批次,例如:
{
"memoryOps": [],
"decision": {
"summary": "先列出候选文档和已选知识文档。",
"reason": "需要先缩小目标文件和规则来源。",
"expectedOutcome": "拿到最新 PRD 文档路径和可检索的规则文档。"
},
"nextAction": {
"action": "hand",
"parallelCallToolParam": [
{
"toolName": "terminal_run_command",
"args": {
"command": "find docs/prd -name '*Alpha*PRD*.docx' -maxdepth 2",
"cwd": "/project-alpha"
}
},
{
"toolName": "kb_list_documents",
"args": {
"query": "Alpha 发布模板 评审意见 术语"
}
}
]
}
}
为什么这批工具可以并行:
terminal_run_command在这里是低风险只读命令kb_list_documents是只读- 两者都能通过
validateParallelToolCalls()
hand 会并行执行它们,并把结果写进:
lastParallelCallToolResultstrace.steps
这一步还不会改 brainMemory.inside。工具结果先原样落在 lastParallelCallToolResults,等下一轮 brain 再提炼成 evidences/facts。
2.5.4 brain 第四次决策,再做一批文档读取
上一轮工具结果回来后,brain 会把高价值信息提炼进 memory:
{
"memoryOps": [
{
"op": "add",
"field": "evidences",
"value": {
"id": "ev-docx-candidates",
"toolName": "terminal_run_command",
"summary": "候选文档包含 docs/prd/Alpha/PRD-Alpha-v7.docx,且它是最近修改的版本。",
"sourceType": "tool_result",
"rawRef": "tool_result:terminal_run_command#1"
}
},
{
"op": "add",
"field": "evidences",
"value": {
"id": "ev-kb-docs",
"toolName": "kb_list_documents",
"summary": "已选知识文档中可直接引用《品牌术语规范》和《发布说明模板》。",
"sourceType": "tool_result",
"rawRef": "tool_result:kb_list_documents#1"
}
},
{
"op": "add",
"field": "facts",
"value": {
"id": "fact-target-docx",
"summary": "当前最可能的目标文档是 docs/prd/Alpha/PRD-Alpha-v7.docx。",
"evidenceIds": ["ev-docx-candidates"]
}
}
],
"decision": {
"summary": "继续打开并检查目标 docx 的结构。",
"reason": "还需要确认发布日期说明和术语相关段落在哪里。",
"expectedOutcome": "得到后续 planning 可复用的文档定位信息。"
},
"nextAction": {
"action": "hand",
"parallelCallToolParam": [
{
"toolName": "docx_open",
"args": {
"sourcePath": "/project-alpha/docs/prd/Alpha/PRD-Alpha-v7.docx"
}
}
]
}
}
docx_open 结束后,下一轮 brain 会拿到一个 docId,然后再发第二批只读并行调用:
{
"nextAction": {
"action": "hand",
"parallelCallToolParam": [
{
"toolName": "docx_outline",
"args": {
"docId": "doc-alpha-v7"
}
},
{
"toolName": "docx_search",
"args": {
"docId": "doc-alpha-v7",
"query": "发布日期 术语 品牌名",
"mode": "literal"
}
}
]
}
}
2.5.5 brain 最后转向 mouth
当 brain 认为上下文已经够用时,会把结论沉淀成:
- 已确认事实
- 可复用证据
- 未决项
然后进入 mouth,deliveryMode=context_note。
这次 mouth 的代表性交付可以是:
# 上下文补充结果
## 本轮结论
已确认目标文档、两份规则来源和当前最可能需要修改的两个区域;发布日期最终值仍需结合最终发布窗口确认。
## 可复用证据
- 目标文档为 `docs/prd/Alpha/PRD-Alpha-v7.docx`,它是当前候选中最新修改版本。
- 已选知识范围内可直接引用《品牌术语规范》。
- 已选知识范围内可直接引用《发布说明模板》。
- 目标文档中已有“发布日期”说明段和多处品牌术语段落。
## 当前边界
- 当前已经能进入 planning 和执行阶段。
- 仍不能从现有输入中确认最终发布日期具体值。
## 未决项
- 最终发布日期是否已经由业务锁定。
## 建议补充方向
- planning 时把“读取目标 block”“提取规则”“执行 docx 修改与交付”拆开。
这时 context-1 的 HumanTaskOutput 大致是:
{
taskId: 'context-1',
status: 'completed',
summary: '已确认目标文档和规则来源,但发布日期具体值仍待确认。',
deliveryMarkdown: '...context note...',
artifacts: undefined
}
2.6 director_context_human 回写了什么
buildContextEnrichPatch() 不会把整段 context_note 塞回 DirectorState。它只会:
- 找到
## 可复用证据 - 提取 bullet / numbered line
- 去重后合并进
contextEvidence
所以 patch 类似:
{
contextRounds: 1,
contextEvidence: [
'目标文档为 `docs/prd/Alpha/PRD-Alpha-v7.docx`,它是当前候选中最新修改版本。',
'已选知识范围内可直接引用《品牌术语规范》。',
'已选知识范围内可直接引用《发布说明模板》。',
'目标文档中已有“发布日期”说明段和多处品牌术语段落。'
],
contextObjective: undefined,
directorStage: 'director_intent'
}
这一步非常关键:复杂路径里,director 和后续 human 真正复用的是 contextEvidence,不是整个 context_note。
2.7 第 3 步:director_intent 第二次路由
现在再次进入 director_intent。这次它看到:
contextRounds = 1- 已经有一组
contextEvidence - 用户要求仍然是多动作、多产物的修改型任务
于是更可能返回:
{
"mode": "to_plan",
"reason": "当前证据已足够,但任务仍包含读取、规则提取、文档修改、交付四类动作,适合拆成多个 human 闭环任务。"
}
patch:
{
contextObjective: undefined,
directorStage: 'director_plan'
}
2.8 第 4 步:director_plan
director_plan 会读取原始输入、历史、contextEvidence、selectedScopeSummary,再生成任务列表。
在这个样例里,合法计划可以是:
[
{
id: 'task-1',
title: '确认待改 block',
userInput:
'读取 docs/prd/Alpha/PRD-Alpha-v7.docx,定位与“发布日期说明”和品牌术语统一相关的 block,输出可直接修改的区块清单和当前文本。'
},
{
id: 'task-2',
title: '提取执行规则',
userInput:
'从已选知识范围中提取《品牌术语规范》和《发布说明模板》里与术语统一、发布日期说明相关的可执行规则,输出规则清单和引用证据。'
},
{
id: 'task-3',
title: '修改并产出文档交付物',
userInput:
'基于 task-1 的区块清单和 task-2 的规则,修改 docs/prd/Alpha/PRD-Alpha-v7.docx,生成预览,保存到 docs/output/PRD-Alpha-reviewed.docx,并说明仍需人工确认的内容。'
}
]
通过 validatePlannerTasks() 后,toPlanTasks() 会回写:
{
plan: [
{ id: 'task-1', title: '确认待改 block', userInput: '...', status: 'ready' },
{ id: 'task-2', title: '提取执行规则', userInput: '...', status: 'ready' },
{ id: 'task-3', title: '修改并产出文档交付物', userInput: '...', status: 'ready' }
],
dispatchCursor: 0,
directorStage: 'director_plan_exec'
}
2.9 第 5 步:director_plan_exec 执行 task-1
pickPendingTaskIndex() 先选到 task-1。director_plan_exec 会把它包装成 task_handoff 类型的 human 子任务。
2.9.1 task-1 的 human 关键过程
这个 human 是全新的,不继承 context-1 的 HumanState。
典型闭环:
brain决定学习docx-readlearning装入docx_open / docx_outline / docx_search / docx_get_block / docx_get_historybrain -> hand调用docx_openbrain -> hand并行调用docx_outline + docx_searchbrain把 block 定位结果提炼成facts/evidencesbrain -> mouthmouth用deliveryMode=task_handoff输出交接文档
代表性 deliveryMarkdown:
# 子任务交付
## 本任务结论
已定位两个需要修改的区域:发布日期说明段和品牌术语说明段。
## 已完成
- 已确认需修改 block 的位置和当前文本。
## 关键事实
- 发布日期说明位于“发布计划”章节下第 3 个 block。
- 品牌术语不一致问题主要集中在“产品介绍”章节。
## 关键证据
- docx_outline: 文档包含“产品介绍”“发布计划”等章节。
- docx_search: 搜索命中“发布日期”“品牌名”相关段落。
## 产物与引用
无
## 未决与风险
- 最终发布日期具体值仍需业务确认。
## 对后续任务的影响
- 后续修改任务可以直接对两个 block 动手,不需要再全量扫描文档。
## 建议下一步
- 结合知识规则,对上述两个 block 生成结构化修改操作。
2.9.2 director 回写 task-1
buildPlanExecPatch() 只更新当前 task:
{
plan: [
{
id: 'task-1',
status: 'completed',
deliveryMarkdown: '...task-1 handoff...',
artifacts: undefined
},
{ id: 'task-2', status: 'ready' },
{ id: 'task-3', status: 'ready' }
],
dispatchCursor: 1,
directorStage: 'director_plan_exec'
}
2.10 director_plan_exec 执行 task-2
task-2 的目标是只做规则提取,不碰 docx。
2.10.1 task-2 的 human 关键过程
典型闭环:
brain决定学习kb-evidence-querylearning装入kb_hybrid_search_chunks / kb_vector_search_chunks / kb_fts_search_chunksbrain -> hand发一个只读并行批次,例如kb_hybrid_search_chunks + kb_fts_search_chunksbrain根据检索结果写入evidences/factsmouth产出task_handoff
这一步的重点是:
lastParallelCallToolResults会同时持有两条 KB 结果brain负责把 chunk 结果提炼成“规则”- 规则不会直接写回
DirectorState,而是先留在task-2.deliveryMarkdown
代表性交付:
# 子任务交付
## 本任务结论
已提炼出可直接执行的两类规则:品牌术语统一规则、发布日期说明规则。
## 已完成
- 已从已选知识范围内提取规则。
## 关键事实
- 品牌术语必须统一为正式品牌名“NCurator Desktop”。
- 发布日期说明应使用“预计发布日期 + 待最终确认提示”的写法。
## 关键证据
- kb_hybrid_search_chunks: 《品牌术语规范》命中正式品牌名规则。
- kb_fts_search_chunks: 《发布说明模板》命中发布日期说明模板。
## 产物与引用
无
## 未决与风险
- 如果业务已经锁定发布日期,需要覆盖模板中的“预计”表述。
## 对后续任务的影响
- 修改任务可直接套用规则,不必再次检索。
## 建议下一步
- 对目标 block 生成 docx 修改操作,并在交付前做 validate 与 preview。
director 回写后,状态变成:
{
dispatchCursor: 2,
plan: [
{ id: 'task-1', status: 'completed', deliveryMarkdown: '...' },
{ id: 'task-2', status: 'completed', deliveryMarkdown: '...' },
{ id: 'task-3', status: 'ready' }
],
directorStage: 'director_plan_exec'
}
2.11 director_plan_exec 执行 task-3
task-3 是整个复杂样例里最重要的一段,因为这里既有 docx 修改,也有 artifacts。
先明确一个容易误读的点:
plan[].userInput保留的是 planner 产出的原始子任务描述,不会被前序任务结果原地改写。- 但
director_plan_exec真正派发给这次human的task.userInput,会通过buildPlanTaskObjective()重新组织。 - 重新组织后的输入里,会显式带上
contextEvidence和前序任务的deliveryMarkdown。
也就是说,task-3 实际收到的不是只有这一句:
基于 task-1 的区块清单和 task-2 的规则,修改 docs/prd/Alpha/PRD-Alpha-v7.docx,生成预览,保存到 docs/output/PRD-Alpha-reviewed.docx,并说明仍需人工确认的内容。
而是更接近下面这种 dispatch 输入:
# 计划子任务
请完成当前子任务,并输出供后续任务继续执行的正式交接文档。
不要直接给最终用户写总结性答复;你的交付应帮助下一个 human 或 synthesis human 继续工作。
## 总体目标
请把 Alpha PRD 的最新 docx 按上周评审意见和当前发布模板修好……
## 当前子任务
修改并产出文档交付物
基于 task-1 的区块清单和 task-2 的规则,修改 docs/prd/Alpha/PRD-Alpha-v7.docx,生成预览,保存到 docs/output/PRD-Alpha-reviewed.docx,并说明仍需人工确认的内容。
## 已选资料范围
workspace=/project-alpha
selected scopes:
- workspace directory: docs/prd
- kb document: 品牌术语规范
- kb document: 发布说明模板
## 上下文证据
1. 已确认目标 docx 为 docs/prd/Alpha/PRD-Alpha-v7.docx
2. 已确认评审意见与模板规则来源
## 前序任务交付
### 任务 1: 确认待改 block
- taskId: task-1
- 状态: completed
- 正式交付:
...task-1 的 deliveryMarkdown...
### 任务 2: 提取执行规则
- taskId: task-2
- 状态: completed
- 正式交付:
...task-2 的 deliveryMarkdown...
所以这里真正跨 task 传递的,不是上一个 human 的内部状态,而是前序任务已经落到 plan[].deliveryMarkdown 的正式交付文本,再由 director 重新包装进下一个任务的 userInput。
2.11.1 task-3 的 human 前半段:先学能力
由于这是新的 human,它依然从空的 learnedSkillIds 开始。
第一轮通常会学习:
['docx-read', 'docx-write', 'docx-delivery']
2.11.2 task-3 的 hand 先触发一次并行保护
为了展示 hand 的守卫逻辑,先模拟一个真实可发生的错误决策:
brain 试图把下面两个动作放进同一批:
[
{
"toolName": "docx_open",
"args": {
"sourcePath": "/project-alpha/docs/prd/Alpha/PRD-Alpha-v7.docx"
}
},
{
"toolName": "docx_validate",
"args": {
"docId": "doc-alpha-v7"
}
}
]
这批动作会被 validateParallelToolCalls() 拒绝,因为:
docx_open不是只读并行安全工具docx_validate还依赖docx_open之后拿到的docId
所以 hand 不会真正执行它们,而是回写:
{
watchHint: 'hand 收到不满足并行安全要求的工具批次,已返回 brain 重新决策。',
runtime.stage: 'brain'
}
这时你能看到一个很关键的实现事实:
watchHint不是单独的节点- 它只是一个轻量纠偏信号
- 真正做下一步决策的仍然是下一轮
brain
2.11.3 task-3 的 human 后半段:拆成顺序执行
修正后的闭环会变成:
brain -> hand:docx_openbrain -> hand:docx_apply_opsbrain -> hand:docx_validatebrain -> hand:docx_render_previewbrain -> hand:docx_savebrain -> mouth
其中最重要的是 runtimeArtifacts 的变化。
在 docx_open / docx_apply_ops / docx_validate 之后:
- 还没有可供最终消费的 file artifact
runtimeArtifacts仍可能是空
在 docx_render_preview 成功后:
runtimeArtifacts = [
{
kind: 'file',
capability: 'docx',
path: '/project-alpha/.ncurator/docx-previews/run-a-doc-alpha-v7-preview.docx',
fileName: 'run-a-doc-alpha-v7-preview.docx',
operation: 'created',
artifactRole: 'preview',
summary: 'preview generated (docx)'
}
]
在 docx_save 成功后:
runtimeArtifacts = [
{
kind: 'file',
capability: 'docx',
path: '/project-alpha/.ncurator/docx-previews/run-a-doc-alpha-v7-preview.docx',
fileName: 'run-a-doc-alpha-v7-preview.docx',
operation: 'created',
artifactRole: 'preview',
summary: 'preview generated (docx)'
},
{
kind: 'file',
capability: 'docx',
path: '/project-alpha/docs/output/PRD-Alpha-reviewed.docx',
fileName: 'PRD-Alpha-reviewed.docx',
operation: 'created',
artifactRole: 'committed',
summary: 'doc saved: PRD-Alpha-reviewed.docx'
}
]
这里要注意两个边界:
- artifacts 是
hand从工具结构化结果里提取出来的 mouth不负责“生成 artifacts”,只负责把已有 artifacts 透传到result
2.11.4 task-3 的 task_handoff
这次 mouth 仍然是 task_handoff,所以它写的是给后续 synthesis 用的交接,而不是最终答复。
代表性交付:
# 子任务交付
## 本任务结论
已根据前两步提供的 block 清单和规则修改目标文档,完成校验、预览生成和落盘。
## 已完成
- 已统一品牌术语写法。
- 已补充发布日期说明段。
- 已生成 preview 并保存最终文件。
## 关键事实
- 修改发生在“产品介绍”和“发布计划”两个区域。
- 校验未发现结构性问题。
## 关键证据
- docx_apply_ops: 已完成目标 block 的结构化修改。
- docx_validate: 当前快照校验通过。
- docx_render_preview: 已生成预览产物。
- docx_save: 已写入最终文件。
## 产物与引用
无
## 未决与风险
- 发布日期具体值仍使用“预计”表述,待业务最终确认。
## 对后续任务的影响
- synthesis 可以直接向用户说明修改结果,并提示未决项。
## 建议下一步
- 汇总全部任务结果,向用户交付最终说明。
HumanTaskOutput.artifacts 会带上 preview 和 committed file 两个 artifact。
2.11.5 director 回写 task-3
这时 buildPlanExecPatch() 会回写:
{
plan: [
{ id: 'task-1', status: 'completed', deliveryMarkdown: '...', artifacts: undefined },
{ id: 'task-2', status: 'completed', deliveryMarkdown: '...', artifacts: undefined },
{
id: 'task-3',
status: 'completed',
deliveryMarkdown: '...task-3 handoff...',
artifacts: [
{ kind: 'file', path: '/project-alpha/.ncurator/docx-previews/run-a-doc-alpha-v7-preview.docx', artifactRole: 'preview', ... },
{ kind: 'file', path: '/project-alpha/docs/output/PRD-Alpha-reviewed.docx', artifactRole: 'committed', ... }
]
}
],
dispatchCursor: 3,
directorStage: 'director_synthesis_human'
}
为什么直接进 director_synthesis_human:
plan里已经没有readytaskdirector_plan_exec当前实现是串行推进
2.12 第 6 步:director_synthesis_human
这一步会再派发一个新的 human:
{
taskId: 'synthesis-human',
deliveryMode: 'user_answer',
streamAnswer: true
}
这个 human 读到的 userInput 不是原始一句话,而是 buildSynthesisHumanObjective() 组织出来的大对象,里面包含:
- 原始用户请求
selectedScopeSummarycontextEvidence- 全部
plan[].deliveryMarkdown
2.12.1 synthesis human 的实际意义
这里的 human 不一定还要继续调工具。很多情况下:
brain看到现成的contextEvidence- 又看到三个
task_handoff - 已经足够整理最终答案
所以它可以直接:
brainmouth- 结束
这也正是当前架构的关键:即使复杂路径最后也回到 human 完成最终交付,而不是 director 自己拼答案。
2.12.2 synthesis 的最终交付
代表性最终答复:
# 处理结果
已完成 Alpha PRD 文档的定向修改,并生成预览与最终保存文件。
## 已确认事实
- 目标文档是 `docs/prd/Alpha/PRD-Alpha-v7.docx`。
- 已按知识规则统一品牌术语写法。
- 已补充发布日期说明并保留“待最终确认”的边界。
- 已生成 preview,并保存最终文件 `docs/output/PRD-Alpha-reviewed.docx`。
## 关键证据
- context evidence: 已定位目标 docx 和规则来源。
- task-1: 已定位需修改的 block。
- task-2: 已提炼术语与发布日期规则。
- task-3: 已完成 docx 修改、校验、预览和保存。
## 限制与未决项
- 最终发布日期具体值仍需业务确认;当前文案按“预计发布日期”处理。
2.13 synthesis 回写 finalArtifacts
buildSynthesisHumanPatch() 会做两件事:
finalDeliveryMarkdown = synthesisHumanOutput.deliveryMarkdownfinalArtifacts = mergeArtifactRefs(plan 中已有 artifacts, synthesis output artifacts)
在这个样例里,synthesis 自己没有新增 artifact,所以最终结果主要来自 task-3:
{
finalArtifacts: [
{
kind: 'file',
capability: 'docx',
path: '/project-alpha/.ncurator/docx-previews/run-a-doc-alpha-v7-preview.docx',
artifactRole: 'preview',
summary: 'preview generated (docx)'
},
{
kind: 'file',
capability: 'docx',
path: '/project-alpha/docs/output/PRD-Alpha-reviewed.docx',
artifactRole: 'committed',
summary: 'doc saved: PRD-Alpha-reviewed.docx'
}
],
directorStage: 'director_complete'
}
2.14 第 7 步:director_complete
最后 director_complete 只做 run 级收尾:
{
status: 'completed',
directorStage: 'director_complete'
}
到这里,复杂样例完整结束。
2.15 样例 A 的事件流按顺序看是什么
如果忽略 token streaming,只看关键事件,大致顺序是:
task_started(context-1)human_step(human_brain),随后一次watch_hint(memory_reducer)human_step(human_learning)tool_call_started/result若干human_step(human_hand)若干task_delivery_ready(context-1, internal=true)task_completed(context-1)plan_createdtask_started(task-1)task_delivery_ready(task-1)task_completed(task-1)task_started(task-2)task_delivery_ready(task-2)task_completed(task-2)task_started(task-3)- 一次
watch_hint(hand) file_artifact(preview)file_artifact(committed)task_delivery_ready(task-3)task_completed(task-3)task_started(synthesis-human)answer_completed(synthesis-human)task_delivery_ready(synthesis-human)task_completed(synthesis-human)
其中最值得记住的三点:
context-1的交付是内部交付,不是最终答复plan阶段每个 task 的正式产出都先进入deliveryMarkdown- 只有 direct / synthesis 这类
user_answer + streamAnswer=true的任务会走answer_completed
3. 样例 B:默认 direct-human 路径
3.1 用户请求
假设用户输入:
基于已选知识范围,总结《发布说明模板》里对“发布日期”字段的写法要求,给我一个简短结论。
这类请求的特点:
- 不需要修改 docx
- 不需要多任务拆分
- 单个
human闭环即可完成
3.2 初始 AiRunState
初始状态与样例 A 一样,只是 input 不同:
{
runId: 'run-b',
sessionId: 'session-b',
status: 'running',
input: '基于已选知识范围,总结《发布说明模板》里对“发布日期”字段的写法要求,给我一个简短结论。',
directorStage: 'director_intent',
contextRounds: 0,
contextEvidence: [],
plan: [],
dispatchCursor: 0,
finalArtifacts: []
}
3.3 director_intent 直接分流到 director_direct_human_exec
这次 director_intent 可能直接给出:
{
"mode": "direct_human",
"reason": "任务目标单一,单个 human 可通过已选知识范围直接检索并完成答复。"
}
patch:
{
contextObjective: undefined,
directorStage: 'director_direct_human_exec'
}
这里不会经过:
director_context_humandirector_plandirector_plan_execdirector_synthesis_human
3.4 director_direct_human_exec
director_direct_human_exec 会派发:
{
taskId: 'direct-human',
deliveryMode: 'user_answer',
streamAnswer: true
}
3.5 direct-human 的 human 如何闭环
一条典型的真实链路:
brain先判断需要知识检索learning学习kb-evidence-queryhand调用kb_hybrid_search_chunksbrain把 chunk 结果提炼成 facts/evidencesmouth用deliveryMode=user_answer输出正式答复
代表性的 HumanTaskOutput:
{
taskId: 'direct-human',
status: 'completed',
summary: '已总结发布日期字段的写法要求。',
deliveryMarkdown: '# 处理结果\n\n发布日期字段应采用“预计发布日期 + 待最终确认说明”的写法……',
artifacts: undefined
}
3.6 director_direct_human_exec 回写什么
buildDirectHumanPatch() 直接回写:
{
finalDeliveryMarkdown: '# 处理结果 ...',
finalArtifacts: [],
directorStage: 'director_complete'
}
然后 director_complete 把 run 置为:
{
status: 'completed',
directorStage: 'director_complete'
}
3.7 样例 B 的关键差异
这条路径里:
contextEvidence全程为空也没关系plan全程为空finalDeliveryMarkdown直接来自这一个human- 没有中间
task_handoff - 没有 synthesis human
事件流也简单很多:
task_started(direct-human)human_step(human_brain)human_step(human_learning)tool_call_started/resulthuman_step(human_hand)answer_completed(direct-human)task_delivery_ready(direct-human)task_completed(direct-human)
4. 两条路径放在一起看
4.1 director 侧差异
| 对比项 | 样例 A 复杂路径 | 样例 B 默认路径 |
|---|---|---|
| 首次路由 | need_context |
direct_human |
| 是否补上下文 | 是 | 否 |
| 是否拆计划 | 是 | 否 |
是否多次派发 human |
是 | 否 |
| 是否需要 synthesis | 是 | 否 |
finalDeliveryMarkdown 来源 |
synthesis human | direct human |
4.2 human 侧共性
不管是复杂路径还是默认路径,真正执行任务的始终是 human,并且都遵守同一套内部原则:
brain是唯一推理节点learning只学 skill,不推理hand只执行工具,不改brainMemory.insidemouth只交付,不新增 facts,也不生成 artifacts
4.3 数据流真正怎么过
复杂路径里,跨子任务传递的数据只走这三条通道:
context_note -> extractReusableContextEvidence() -> contextEvidencetask_handoff -> plan[].deliveryMarkdownhand提取的artifacts -> plan[].artifacts -> finalArtifacts
默认路径里,这三条通道会被大幅裁剪,只剩:
direct human -> finalDeliveryMarkdowndirect human -> finalArtifacts
5. 最容易改错的实现点
5.1 改 director 逻辑时
- 不要让
director直接依赖HumanState - 不要把整个
context_note原样塞回DirectorState - 不要让
director_plan生成“最终总结任务”
5.2 改 human 逻辑时
brainMemory.inside只能通过memoryOps + reduceBrainMemory()修改watchHint只是纠偏信号,不是新节点runtimeArtifacts只应在hand增长mouth透传 artifacts,但不负责制造 artifacts
5.3 改交付逻辑时
deliveryMode只影响交付组织方式context_note是给director的task_handoff是给后续 task 和 synthesis 的user_answer才是面向最终用户的
6. 看代码时建议对照的文件
src/main/services/ai/AiRunService.tssrc/main/services/ai/runtime.tssrc/main/services/ai/director/graph/directorGraph.tssrc/main/services/ai/director/node/intentNode.tssrc/main/services/ai/director/node/contextEnrichNode.tssrc/main/services/ai/director/node/planNode.tssrc/main/services/ai/director/node/planExecNode.tssrc/main/services/ai/director/node/synthesisHumanNode.tssrc/main/services/ai/director/node/helpers.tssrc/main/services/ai/director/node/objectives.tssrc/main/services/ai/human/graph/humanGraph.tssrc/main/services/ai/human/node/brainNode/index.tssrc/main/services/ai/human/node/learningNode/index.tssrc/main/services/ai/human/node/handNode/index.tssrc/main/services/ai/human/node/mouthNode/index.tssrc/main/services/ai/human/node/utils/stateTransitions.tssrc/main/services/ai/human/node/utils/events.tssrc/main/services/ai/human/node/mouthNode/deliveryMarkdown.tssrc/main/services/ai/utils/artifacts.ts
如果你后面要改 AI 逻辑,最有用的阅读顺序通常是:
- 先看本文,确认你想改的是哪一段真实链路
- 再看
implementation-spec.md,确认节点职责边界 - 最后进对应代码文件改实现