Refine AI implementation spec and data flow guidance
This commit is contained in:
1 parent
683fb8d385
commit
403fc0d706
3 files changed
+481
-434
No files matched your search
@@ -4,7 +4,7 @@
|
||||
|
||||
- 当涉及技术栈、整体架构、进程边界、本地优先约束或混合检索链路时,请阅读 `doc/architecture.md`
|
||||
- 当涉及 AI 总体架构、Main/Human 分工、复杂度路由、主流程或整体协作关系时,请阅读 `doc/ai-design/architecture.md`
|
||||
- 当涉及 `main`/`human` 的实现规则、节点行为、关键伪代码、`deliveryMode`、`watchHint`、`artifacts` 责任边界、最近决策历史、`task_handoff` 或 `context_note` 的运行语义时,请阅读 `doc/ai-design/implementation-spec.md`
|
||||
- 当涉及 `main`/`human` 的实现规则、节点行为、状态转移、`deliveryMode`、`watchHint`、`artifacts` 责任边界、最近决策历史、`task_handoff` / `context_note` 运行语义,或 `LangGraph`、`chatStructured` 等 AI 技术实现约束时,请阅读 `doc/ai-design/implementation-spec.md`
|
||||
- 当涉及 `main`/`human` 状态字段、数据契约、`deliveryMarkdown` 交接结构、`contextEvidence` 提取规则、`fact/evidence` 引用约束、`artifacts` 契约、Main/Human 数据流转或流转图时,请阅读 `doc/ai-design/data-definition-and-flow.md`
|
||||
- 当涉及 AI 未来重构规划、迭代方向、未决方案、下一次准备怎么改时,请阅读 `doc/ai-design/refactor-plan.md`
|
||||
- 当涉及 `human` 的 skill 集合、skill 命名、skill 拆分边界、`skillCatalog` / `skillRegistry` 或 skill 与 tools 实现边界时,请阅读 `doc/ai-design/human-skill-design.md`
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
- `main` 负责 run 级编排、计划调度和最终汇总。
|
||||
- `human` 负责单任务闭环执行。
|
||||
- 节点行为、技术实现约束与跨层规则见 `implementation-spec.md`。
|
||||
- 本文只定义状态、契约与数据流转,不展开架构动机与阅读导引。
|
||||
|
||||
## 2. `main` 数据定义
|
||||
|
||||
@@ -1,502 +1,548 @@
|
||||
# NCurator-Desktop AI 实现规范
|
||||
|
||||
本文定义 `main + human` 的实现规则。代码实现应优先对齐本文档。
|
||||
本文定义 `main + human` 的当前实现契约。代码实现应优先对齐本文档;本文中的状态转移只描述 graph 行为
|
||||
|
||||
## 1. `main` 实现主线
|
||||
## 1. `main`
|
||||
|
||||
### 1.1 `main` 主循环
|
||||
### 1.1 运行模型
|
||||
|
||||
```ts
|
||||
function runMain(input) {
|
||||
const route = runMainIntent(input)
|
||||
- `main` 以 `@langchain/langgraph` 的 `StateGraph` 为运行骨架,状态 schema 由 `main/state/schema.ts` 提供。
|
||||
- `main` graph 的节点返回 `Partial<AiRunState>` 形式的 state patch,由 graph 负责状态推进。
|
||||
- `main` 的条件路由由 `MainState.mainStage` 和 `MainState.status` 决定,不在 graph 外额外手写控制循环。
|
||||
|
||||
if (route === 'direct_human') {
|
||||
const output = dispatchHuman(
|
||||
{ taskId: 'direct', userInput: input },
|
||||
{ deliveryMode: 'user_answer' }
|
||||
)
|
||||
return completeRun(output)
|
||||
}
|
||||
状态转移:
|
||||
|
||||
const contextOutput = dispatchHuman(
|
||||
{ taskId: 'context', userInput: buildContextObjective(input) },
|
||||
{ deliveryMode: 'context_note' }
|
||||
)
|
||||
|
||||
const contextEvidence = extractContextEvidence(contextOutput)
|
||||
const plan = buildPlan(input, contextEvidence)
|
||||
const taskOutputs = []
|
||||
|
||||
for (const task of plan) {
|
||||
const output = dispatchHuman(task, { deliveryMode: 'task_handoff' })
|
||||
taskOutputs.push(output)
|
||||
updatePlanState(task, output)
|
||||
|
||||
if (output.status === 'blocked') {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const finalOutput = dispatchHuman(
|
||||
{ taskId: 'synthesis', userInput: buildSynthesisObjective(input, taskOutputs) },
|
||||
{ deliveryMode: 'user_answer' }
|
||||
)
|
||||
|
||||
return completeRun(finalOutput)
|
||||
}
|
||||
```text
|
||||
START -> main_intent
|
||||
main_intent -> main_direct_human_exec | main_context_human | main_plan | main_plan_exec | main_synthesis_human | main_complete
|
||||
main_context_human -> main_intent
|
||||
main_direct_human_exec -> main_complete
|
||||
main_plan -> main_plan_exec | main_complete
|
||||
main_plan_exec -> main_plan_exec | main_synthesis_human
|
||||
main_synthesis_human -> main_complete
|
||||
main_complete -> END
|
||||
```
|
||||
|
||||
对应数据结构:
|
||||
|
||||
- `MainState`
|
||||
- `MainPlanTask`
|
||||
- `AiArtifactRef`
|
||||
- `HumanTaskInput`
|
||||
- `HumanRunDispatchOptions`
|
||||
- `HumanTaskOutput`
|
||||
|
||||
对应流转图:
|
||||
|
||||
- `main` 直接路径:`direct human`
|
||||
- `main` 阶段一:输入进入 `context / plan`
|
||||
- `main` 阶段二:计划派发、结果回写、最终收尾
|
||||
|
||||
### 1.2 `main_intent`
|
||||
|
||||
```ts
|
||||
function runMainIntent(input) {
|
||||
if (canBeHandledBySingleHuman(input)) {
|
||||
return 'direct_human'
|
||||
}
|
||||
|
||||
return 'complex_pipeline'
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- 默认优先选择 `direct_human`。
|
||||
- 只有当当前单 `human` 直接闭环明显不稳时,才进入复杂链路。
|
||||
- `main_intent` 只做 orchestration 复杂度判断,不负责展开求解。
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:
|
||||
- graph 起点
|
||||
- `main_context_human` 返回后重新判断
|
||||
- 读取:
|
||||
- `MainState.input`
|
||||
- `MainState.contextRounds`
|
||||
- `MainState.contextEvidence`
|
||||
- 会话历史摘要与最近对话
|
||||
- `ctx.selectedScopeSummary`
|
||||
- 回写:
|
||||
- `MainState.mainStage`
|
||||
- 必要时回写 `MainState.contextObjective`
|
||||
- 下一跳:
|
||||
- `main_direct_human_exec`
|
||||
- `main_context_human`
|
||||
- `main_plan`
|
||||
- 实现约束:
|
||||
- 节点入口先调用 `ctx.checkCancelled()`
|
||||
- 超过上下文补充轮次上限时,直接进入 `main_plan`
|
||||
- 模型输出必须通过 `ctx.chatStructured`
|
||||
- prompt 必须通过 `buildIntentPrompt` 组织
|
||||
- 节点返回值应保持为 `Partial<AiRunState>`
|
||||
- 对应数据结构:
|
||||
- `MainState.input`
|
||||
- `MainState.contextRounds`
|
||||
- `MainState.contextObjective`
|
||||
- `MainState.mainStage`
|
||||
- 对应流转图:
|
||||
- `main` 直接路径:`direct human`
|
||||
- `main` 阶段一:输入进入 `context / plan`
|
||||
|
||||
### 1.3 `main_context_human`
|
||||
|
||||
```ts
|
||||
function buildContextObjective(input) {
|
||||
return describeWhatContextStageNeedsToClarify(input)
|
||||
}
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`MainState.mainStage = main_context_human`
|
||||
- 读取:
|
||||
- `MainState.input`
|
||||
- `MainState.contextObjective`
|
||||
- `MainState.contextEvidence`
|
||||
- `MainState.contextRounds`
|
||||
- 调用:
|
||||
- 派发一个 `human` 任务
|
||||
- `deliveryMode = context_note`
|
||||
- `internalDelivery = true`
|
||||
- 回写:
|
||||
- `MainState.contextRounds += 1`
|
||||
- `MainState.contextEvidence`
|
||||
- `MainState.contextObjective = undefined`
|
||||
- `MainState.mainStage = main_intent`
|
||||
- 下一跳:
|
||||
- `main_intent`
|
||||
- 实现约束:
|
||||
- `taskId` 使用 `context-${contextRounds + 1}` 形式
|
||||
- `userInput` 通过 `buildContextHumanObjective` 生成,不直接把原始输入原样派发
|
||||
- `contextEvidence` 优先从 `context_note` 的可复用证据区提取
|
||||
- 提取结果通过去重/追加逻辑并入现有 `contextEvidence`
|
||||
- 对应数据结构:
|
||||
- `MainState.contextObjective`
|
||||
- `MainState.contextRounds`
|
||||
- `MainState.contextEvidence`
|
||||
- `HumanTaskInput`
|
||||
- `HumanTaskOutput`
|
||||
- 对应流转图:
|
||||
- `main` 阶段一:输入进入 `context / plan`
|
||||
|
||||
function runContextHuman(input) {
|
||||
return dispatchHuman(
|
||||
{ taskId: 'context', userInput: buildContextObjective(input) },
|
||||
{ deliveryMode: 'context_note' }
|
||||
)
|
||||
}
|
||||
```
|
||||
### 1.4 `main_direct_human_exec`
|
||||
|
||||
实现要求:
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`MainState.mainStage = main_direct_human_exec`
|
||||
- 读取:
|
||||
- `MainState.input`
|
||||
- `MainState.contextEvidence`
|
||||
- 调用:
|
||||
- 派发一个 `human` 任务
|
||||
- `deliveryMode = user_answer`
|
||||
- `streamAnswer = true`
|
||||
- 回写:
|
||||
- `MainState.finalDeliveryMarkdown`
|
||||
- `MainState.finalArtifacts`
|
||||
- `MainState.mainStage = main_complete`
|
||||
- 下一跳:
|
||||
- `main_complete`
|
||||
- 实现约束:
|
||||
- `taskId` 固定为 direct human 专用任务 id
|
||||
- `userInput` 通过 `buildDirectHumanObjective` 生成
|
||||
- `main` 只消费 `HumanTaskOutput`,不直接读取 `human` 内部状态
|
||||
- 对应数据结构:
|
||||
- `MainState.finalDeliveryMarkdown`
|
||||
- `MainState.finalArtifacts`
|
||||
- `HumanTaskOutput`
|
||||
- 对应流转图:
|
||||
- `main` 直接路径:`direct human`
|
||||
|
||||
- `contextObjective` 是本轮上下文补充阶段的临时目标。
|
||||
- `main_context_human` 的输出主要用于提取 `contextEvidence`,不是直接代替最终交付。
|
||||
### 1.5 `main_plan`
|
||||
|
||||
### 1.4 `main_plan`
|
||||
|
||||
```ts
|
||||
function buildPlan(input, contextEvidence) {
|
||||
return splitGoalIntoHumanExecutableTasks(input, contextEvidence)
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- 计划任务必须能交给单个 `human` 闭环执行。
|
||||
- 计划任务的正式交付载体是 `task_handoff`。
|
||||
|
||||
### 1.5 `main_plan_exec`
|
||||
|
||||
```ts
|
||||
function executePlan(plan) {
|
||||
const outputs = []
|
||||
|
||||
for (const task of plan) {
|
||||
const output = dispatchHuman(task, { deliveryMode: 'task_handoff' })
|
||||
outputs.push(output)
|
||||
updatePlanState(task, output)
|
||||
|
||||
if (output.status === 'blocked') {
|
||||
return outputs
|
||||
}
|
||||
}
|
||||
|
||||
return outputs
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- 当前链路按顺序串行派发任务。
|
||||
- 每个任务的 `status / deliveryMarkdown / artifacts` 都要回写到计划状态。
|
||||
- `blocked` 表示任务已经派发且最终阻塞,不与未执行状态混用。
|
||||
|
||||
### 1.5.1 计划任务回写
|
||||
|
||||
```ts
|
||||
function updatePlanState(plan, taskId, output) {
|
||||
const nextPlan = plan.map((task) => {
|
||||
if (task.id !== taskId) {
|
||||
return task
|
||||
}
|
||||
|
||||
return {
|
||||
...task,
|
||||
status: output.status,
|
||||
deliveryMarkdown: output.deliveryMarkdown,
|
||||
artifacts: output.artifacts
|
||||
}
|
||||
})
|
||||
|
||||
const dispatchCursor = findNextReadyTaskIndex(nextPlan)
|
||||
|
||||
return { nextPlan, dispatchCursor }
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- 回写时只更新当前已派发任务。
|
||||
- `dispatchCursor` 始终指向下一项 `ready` 任务;不存在时表示计划阶段已完成。
|
||||
- `task_handoff` 是计划阶段唯一正式交接载体。
|
||||
|
||||
### 1.6 `main_synthesis_human`
|
||||
|
||||
```ts
|
||||
function runSynthesisHuman(input, taskOutputs) {
|
||||
return dispatchHuman(
|
||||
{ taskId: 'synthesis', userInput: buildSynthesisObjective(input, taskOutputs) },
|
||||
{ deliveryMode: 'user_answer' }
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- `main_synthesis_human` 负责汇总多个 `task_handoff`。
|
||||
- 该阶段面向最终用户生成 `user_answer`。
|
||||
|
||||
## 2. `human` 实现主线
|
||||
|
||||
### 2.1 `human` 主循环
|
||||
|
||||
```ts
|
||||
function runHuman(task, options) {
|
||||
const state = initHumanState(task)
|
||||
|
||||
while (state.runtime.status === 'running') {
|
||||
const decision = runBrain(state, options)
|
||||
|
||||
applyMemoryOps(state, decision.memoryOps)
|
||||
state.lastBrainDecision = decision
|
||||
appendBrainDecisionHistory(state, decision)
|
||||
|
||||
if (decision.nextAction.action === 'learning') {
|
||||
runLearning(state, decision.nextAction.wantLearnSkillIds)
|
||||
continue
|
||||
}
|
||||
|
||||
if (decision.nextAction.action === 'hand') {
|
||||
runHand(state, decision.nextAction.parallelCallToolParam)
|
||||
continue
|
||||
}
|
||||
|
||||
if (decision.nextAction.action === 'mouth') {
|
||||
const mouthOutput = runMouth(state, options.deliveryMode)
|
||||
return finalizeHumanResult(state, mouthOutput, decision.nextAction.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- `brain` 是唯一控制中心。
|
||||
- `learning / hand / mouth` 都是执行节点,不扩张为额外推理节点。
|
||||
- `human` 的执行风格是逐步推进直到收敛或稳定阻塞。
|
||||
|
||||
### 2.2 决策历史沉淀
|
||||
|
||||
```ts
|
||||
function appendBrainDecisionHistory(state, decision) {
|
||||
const record = {
|
||||
...decision,
|
||||
createdAt: Date.now()
|
||||
}
|
||||
|
||||
state.brainMemory.outside.brainDecisions = [
|
||||
...state.brainMemory.outside.brainDecisions,
|
||||
record
|
||||
].slice(-10)
|
||||
}
|
||||
|
||||
function buildRecentDecisionDigest(records) {
|
||||
return records.map((record) => ({
|
||||
decision: record.decision,
|
||||
nextAction: record.nextAction,
|
||||
createdAt: record.createdAt
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- `brainDecisions` 只保留最近 10 条。
|
||||
- `brain` 回看历史时只消费 `decision / nextAction / createdAt` 摘要。
|
||||
- `lastBrainDecision` 只服务当前分发,不进入下一轮 `brain` 的直接输入。
|
||||
|
||||
### 2.3 `human` result 收口
|
||||
|
||||
```ts
|
||||
function finalizeHumanResult(state, mouthOutput, status) {
|
||||
state.runtime.status = status
|
||||
state.runtime.stage = 'mouth'
|
||||
state.result = {
|
||||
status,
|
||||
summary: mouthOutput.summary,
|
||||
deliveryMarkdown: mouthOutput.deliveryMarkdown,
|
||||
artifacts: state.runtimeArtifacts
|
||||
}
|
||||
|
||||
return {
|
||||
taskId: state.task.taskId,
|
||||
...state.result
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- `HumanResult.status` 由运行时按 `nextAction.status` 回写。
|
||||
- `runtimeArtifacts` 由运行时透传到 `result`,不是由 `mouth` 模型生成。
|
||||
- `HumanTaskOutput` 是 `main` 与 `human` 之间的稳定交接结构。
|
||||
|
||||
## 3. `brain` 实现规则
|
||||
|
||||
### 3.1 判断顺序
|
||||
|
||||
```ts
|
||||
function runBrain(state) {
|
||||
if (canFinishNow(state)) {
|
||||
return nextActionMouth('completed')
|
||||
}
|
||||
|
||||
if (shouldStopAsBlocked(state)) {
|
||||
return nextActionMouth('blocked')
|
||||
}
|
||||
|
||||
if (needSkillExplanation(state)) {
|
||||
return nextActionLearning(selectRequiredSkills(state))
|
||||
}
|
||||
|
||||
if (knowExactToolCalls(state)) {
|
||||
return nextActionHand(buildToolCalls(state))
|
||||
}
|
||||
|
||||
return nextActionMouth('blocked')
|
||||
}
|
||||
```
|
||||
|
||||
实现要求:
|
||||
|
||||
- 优先判断是否已经可以结束。
|
||||
- 再判断是否需要补 skill。
|
||||
- 最后判断是否需要调用工具。
|
||||
- 当没有足够依据继续推进、也没有明确下一步动作时,应优先收敛为 `blocked`,而不是继续漂移。
|
||||
|
||||
### 3.2 `brain` 输入输出
|
||||
|
||||
- 输入:
|
||||
- `task`
|
||||
- `brainMemory`
|
||||
- `lastParallelCallToolResults`
|
||||
- 最近决策历史摘要
|
||||
- `watchHint`
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`MainState.mainStage = main_plan`
|
||||
- 读取:
|
||||
- `MainState.input`
|
||||
- `MainState.contextEvidence`
|
||||
- 会话历史摘要与最近对话
|
||||
- `ctx.selectedScopeSummary`
|
||||
- 输出:
|
||||
- `memoryOps`
|
||||
- `decision`
|
||||
- `nextAction`
|
||||
- 计划任务列表
|
||||
- 回写:
|
||||
- `MainState.plan`
|
||||
- `MainState.dispatchCursor = 0`
|
||||
- `MainState.mainStage = main_plan_exec`
|
||||
- 失败时回写 `MainState.status = failed`、`MainState.error`
|
||||
- 下一跳:
|
||||
- `main_plan_exec`
|
||||
- `main_complete`
|
||||
- 实现约束:
|
||||
- 模型输出必须通过 `ctx.chatStructured`
|
||||
- prompt 必须通过 `buildPlanPrompt` 组织
|
||||
- 计划结果必须先经过 `validatePlannerTasks` 校验,再转换为 `MainPlanTask`
|
||||
- 每个计划任务都必须能交给单个 `human` 闭环执行
|
||||
- 对应数据结构:
|
||||
- `MainState.plan`
|
||||
- `MainState.dispatchCursor`
|
||||
- `MainPlanTask`
|
||||
- 对应流转图:
|
||||
- `main` 阶段一:输入进入 `context / plan`
|
||||
|
||||
实现要求:
|
||||
### 1.6 `main_plan_exec`
|
||||
|
||||
- `brain` 只通过 `memoryOps` 更新 `brainMemory.inside`。
|
||||
- `brain` 自身不直接读 `lastBrainDecision` 作为回看输入。
|
||||
- 回看材料只使用最近决策历史摘要。
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`MainState.mainStage = main_plan_exec`
|
||||
- 读取:
|
||||
- `MainState.plan`
|
||||
- `MainState.dispatchCursor`
|
||||
- `MainState.input`
|
||||
- `MainState.contextEvidence`
|
||||
- 之前任务的 `deliveryMarkdown`
|
||||
- `ctx.selectedScopeSummary`
|
||||
- 调用:
|
||||
- 按 `dispatchCursor` 串行派发 `human`
|
||||
- `deliveryMode = task_handoff`
|
||||
- 回写:
|
||||
- 当前任务 `status`
|
||||
- 当前任务 `deliveryMarkdown`
|
||||
- 当前任务 `artifacts`
|
||||
- `MainState.dispatchCursor`
|
||||
- `MainState.mainStage`
|
||||
- 下一跳:
|
||||
- `main_plan_exec`
|
||||
- `main_synthesis_human`
|
||||
- 实现约束:
|
||||
- 当前策略是串行 `stop-dispatch`
|
||||
- 没有待执行任务时直接进入 `main_synthesis_human`
|
||||
- 任务派发前先把当前任务标记为 `running`
|
||||
- `blocked` 任务会停止后续业务任务,但仍进入 synthesis 收口
|
||||
- 回写时只更新当前已派发任务,不重写其他任务结果
|
||||
- 对应数据结构:
|
||||
- `MainState.plan`
|
||||
- `MainState.dispatchCursor`
|
||||
- `MainPlanTask.status`
|
||||
- `MainPlanTask.deliveryMarkdown`
|
||||
- `MainPlanTask.artifacts`
|
||||
- `HumanTaskOutput`
|
||||
- 对应流转图:
|
||||
- `main` 阶段二:计划派发、结果回写、最终收尾
|
||||
|
||||
## 4. `memoryReducer` 实现规则
|
||||
### 1.7 `main_synthesis_human`
|
||||
|
||||
```ts
|
||||
function applyMemoryOps(state, memoryOps) {
|
||||
if (!validateIndexes(memoryOps)) {
|
||||
return writeWatchHint(state, 'memory edit index invalid')
|
||||
}
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`MainState.mainStage = main_synthesis_human`
|
||||
- 读取:
|
||||
- `MainState.input`
|
||||
- `MainState.contextEvidence`
|
||||
- `MainState.plan`
|
||||
- `ctx.selectedScopeSummary`
|
||||
- 调用:
|
||||
- 派发一个 `human` 任务
|
||||
- `deliveryMode = user_answer`
|
||||
- `streamAnswer = true`
|
||||
- 回写:
|
||||
- `MainState.finalDeliveryMarkdown`
|
||||
- `MainState.finalArtifacts`
|
||||
- `MainState.mainStage = main_complete`
|
||||
- 下一跳:
|
||||
- `main_complete`
|
||||
- 实现约束:
|
||||
- synthesis 只面向最终用户输出,不再生成新的计划任务
|
||||
- `finalArtifacts` 需要合并计划阶段已有 artifacts 与 synthesis 输出 artifacts
|
||||
- `userInput` 通过 `buildSynthesisHumanObjective` 组织
|
||||
- 对应数据结构:
|
||||
- `MainState.plan`
|
||||
- `MainState.finalDeliveryMarkdown`
|
||||
- `MainState.finalArtifacts`
|
||||
- `HumanTaskOutput`
|
||||
- 对应流转图:
|
||||
- `main` 阶段二:计划派发、结果回写、最终收尾
|
||||
|
||||
const evidenceIdsAfterPatch = simulateEvidenceIdsAfterPatch(state, memoryOps)
|
||||
### 1.8 `main_complete`
|
||||
|
||||
if (!allFactEvidenceRefsValid(state, memoryOps, evidenceIdsAfterPatch)) {
|
||||
return writeWatchHint(state, 'fact/evidence references invalid')
|
||||
}
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`MainState.mainStage = main_complete`
|
||||
- 读取:
|
||||
- `MainState.status`
|
||||
- `MainState.finalDeliveryMarkdown`
|
||||
- `MainState.finalArtifacts`
|
||||
- 回写:
|
||||
- 正常路径下回写 `MainState.status = completed`
|
||||
- 保持 `MainState.mainStage = main_complete`
|
||||
- 下一跳:
|
||||
- `END`
|
||||
- 实现约束:
|
||||
- `main_complete` 只负责 run 级收尾,不再派发模型节点
|
||||
- 已处于 `failed / cancelled / completed` 的 run 不应被二次覆盖
|
||||
- 对应数据结构:
|
||||
- `MainState.status`
|
||||
- `MainState.finalDeliveryMarkdown`
|
||||
- `MainState.finalArtifacts`
|
||||
- 对应流转图:
|
||||
- `main` 直接路径:`direct human`
|
||||
- `main` 阶段二:计划派发、结果回写、最终收尾
|
||||
|
||||
commitInsideMemoryPatch(state, memoryOps)
|
||||
}
|
||||
## 2. `human`
|
||||
|
||||
### 2.1 运行模型
|
||||
|
||||
- `human` 以 `@langchain/langgraph` 的 `StateGraph` 为运行骨架,状态 schema 由 `human/state/schema.ts` 提供。
|
||||
- `human` graph 的节点返回下一版 `HumanState`;调度仍由 graph 决定,不在 runner 外手写循环。
|
||||
- `createHumanGraphRunner` 负责创建 graph、注入 `deliveryMode` 与 runtime cache,并把最终 `HumanState` 转换为 `HumanTaskOutput`。
|
||||
- 当前 runner 使用固定 recursion limit,防止 graph 漂移。
|
||||
|
||||
状态转移:
|
||||
|
||||
```text
|
||||
START -> brain
|
||||
brain -> learning | hand | mouth | brain
|
||||
learning -> brain
|
||||
hand -> brain
|
||||
mouth -> brain | END
|
||||
```
|
||||
|
||||
实现要求:
|
||||
对应数据结构:
|
||||
|
||||
- `facts[].evidenceIds` 只能引用当前仍存在或本轮新增的 evidence id。
|
||||
- 引用校验不得依赖 `memoryOps` 顺序。
|
||||
- 任何会导致悬空 `evidenceIds` 的编辑都视为整组非法编辑。
|
||||
- 非法编辑只写入 `watchHint`,不自动级联修正事实。
|
||||
- `HumanState`
|
||||
- `BrainEvidence`
|
||||
- `BrainFact`
|
||||
- `LastBrainDecision`
|
||||
- `BrainDecisionRecord`
|
||||
- `ToolCallResult`
|
||||
- `HumanResult`
|
||||
- `HumanTaskOutput`
|
||||
- `HumanTraceStep`
|
||||
|
||||
## 5. `learning` 实现规则
|
||||
对应流转图:
|
||||
|
||||
```ts
|
||||
function runLearning(state, wantLearnSkillIds) {
|
||||
for (const skillId of wantLearnSkillIds) {
|
||||
if (alreadyLearned(state, skillId)) {
|
||||
writeWatchHint(state, 'skill already learned')
|
||||
return
|
||||
}
|
||||
- `human` 阶段一:`brain` 读取输入并产出决策
|
||||
- `human` 阶段二:`learning` 路径的数据流转
|
||||
- `human` 阶段三:`hand` 路径的数据流转
|
||||
- `human` 阶段四:`mouth` 路径的数据流转
|
||||
|
||||
appendLearnedSkillId(state, skillId)
|
||||
}
|
||||
}
|
||||
```
|
||||
### 2.2 `brain`
|
||||
|
||||
实现要求:
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`runtime.stage = brain`
|
||||
- 读取:
|
||||
- `HumanState.task`
|
||||
- `HumanState.brainMemory`
|
||||
- `HumanState.lastParallelCallToolResults`
|
||||
- `HumanState.watchHint`
|
||||
- 最近决策历史摘要
|
||||
- 输出:
|
||||
- `LastBrainDecision`
|
||||
- 回写:
|
||||
- `HumanState.brainMemory.inside`
|
||||
- `HumanState.brainMemory.outside.brainDecisions`
|
||||
- `HumanState.lastBrainDecision`
|
||||
- `HumanState.runtime.stage`
|
||||
- `HumanState.watchHint`
|
||||
- `HumanState.trace.steps`
|
||||
- 下一跳:
|
||||
- `learning`
|
||||
- `hand`
|
||||
- `mouth`
|
||||
- 当 memory reducer 产出 `watchHint` 时留在 `brain`
|
||||
- 实现约束:
|
||||
- 模型输出必须通过 `ctx.chatStructured`
|
||||
- prompt 必须通过 `buildBrainPromptContext`
|
||||
- 输出必须先经过 `parseBrainDecisionOutput`
|
||||
- `brainMemory.inside` 的修改必须经过 `reduceBrainMemory`
|
||||
- `brainDecisions` 只保留最近 10 条
|
||||
- `brain` 回看历史时只消费摘要,不直接把 `lastBrainDecision` 重新喂回
|
||||
- `brain` 是唯一推理节点
|
||||
- 对应数据结构:
|
||||
- `HumanState.brainMemory.inside`
|
||||
- `HumanState.brainMemory.outside.brainDecisions`
|
||||
- `LastBrainDecision`
|
||||
- `BrainDecisionRecord`
|
||||
- `BrainMemoryOp`
|
||||
- 对应流转图:
|
||||
- `human` 阶段一:`brain` 读取输入并产出决策
|
||||
|
||||
- `learning` 只负责更新 `learnedSkillIds`。
|
||||
- `skillCatalog` 是目录摘要,不在 `learning` 阶段重新计算。
|
||||
- 重复学习只写 `watchHint`,不做重复动作。
|
||||
### 2.3 `learning`
|
||||
|
||||
## 6. `hand` 实现规则
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`lastBrainDecision.nextAction.action = learning`
|
||||
- 读取:
|
||||
- `lastBrainDecision.nextAction.wantLearnSkillIds`
|
||||
- `brainMemory.outside.learnedSkillIds`
|
||||
- skill registry / loader
|
||||
- 回写:
|
||||
- `brainMemory.outside.learnedSkillIds`
|
||||
- `runtime.stage = brain`
|
||||
- 必要时回写 `watchHint`
|
||||
- `trace.steps`
|
||||
- 下一跳:
|
||||
- `brain`
|
||||
- 实现约束:
|
||||
- `learning` 不调用模型
|
||||
- 未知 skill 或重复学习必须写 `watchHint` 并返回 `brain`
|
||||
- `ensureSkillTools` 负责把 skill 对应工具装入 runtime cache
|
||||
- `learning` 只更新已学习 skill 集合,不扩张为新推理节点
|
||||
- 对应数据结构:
|
||||
- `SkillCatalogItem`
|
||||
- `HumanState.brainMemory.outside.learnedSkillIds`
|
||||
- `HumanTraceStep`
|
||||
- 对应流转图:
|
||||
- `human` 阶段二:`learning` 路径的数据流转
|
||||
|
||||
### 6.1 工具执行
|
||||
### 2.4 `hand`
|
||||
|
||||
```ts
|
||||
function runHand(state, toolCalls) {
|
||||
const results = executeToolCalls(toolCalls)
|
||||
state.lastParallelCallToolResults = results
|
||||
state.runtimeArtifacts = collectRuntimeArtifacts(state.runtimeArtifacts, results)
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`lastBrainDecision.nextAction.action = hand`
|
||||
- 读取:
|
||||
- `lastBrainDecision.nextAction.parallelCallToolParam`
|
||||
- `brainMemory.outside.learnedSkillIds`
|
||||
- runtime cache 中已装载的工具
|
||||
- 回写:
|
||||
- `lastParallelCallToolResults`
|
||||
- `runtimeArtifacts`
|
||||
- `runtime.stage = brain`
|
||||
- 必要时回写 `watchHint`
|
||||
- `trace.steps`
|
||||
- 下一跳:
|
||||
- `brain`
|
||||
- 实现约束:
|
||||
- `hand` 不调用模型
|
||||
- 工具批次先经过 `validateParallelToolCalls`
|
||||
- 工具执行通过 `executeToolCall` 并行完成
|
||||
- tool call count 超限时只写 `watchHint`,由下一轮 `brain` 决定是否收敛
|
||||
- `runtimeArtifacts` 只从工具结构化结果提取并合并
|
||||
- 对应数据结构:
|
||||
- `ToolCallParam`
|
||||
- `ToolCallResult`
|
||||
- `HumanState.runtimeArtifacts`
|
||||
- `AiArtifactRef`
|
||||
- `HumanTraceStep`
|
||||
- 对应流转图:
|
||||
- `human` 阶段三:`hand` 路径的数据流转
|
||||
|
||||
if (exceedsToolCallLimit(state)) {
|
||||
writeWatchHint(state, 'tool call limit exceeded')
|
||||
}
|
||||
}
|
||||
```
|
||||
### 2.5 `mouth`
|
||||
|
||||
### 6.2 `artifacts` 收集
|
||||
- 运行模型:LangGraph 节点
|
||||
- 触发条件:`runtime.stage = mouth`
|
||||
- 读取:
|
||||
- `HumanState.task`
|
||||
- `HumanState.brainMemory.inside`
|
||||
- `HumanState.lastBrainDecision`
|
||||
- `HumanState.runtimeArtifacts`
|
||||
- `deliveryMode`
|
||||
- 输出:
|
||||
- `MouthOutput`
|
||||
- 回写:
|
||||
- `HumanState.result`
|
||||
- `HumanState.runtime.status`
|
||||
- `HumanState.runtime.stage = mouth`
|
||||
- `HumanState.trace.steps`
|
||||
- 下一跳:
|
||||
- 结束当前 `human` run
|
||||
- 少数需要继续运行的特殊场景才允许回到 `brain`
|
||||
- 实现约束:
|
||||
- 最终状态由 `lastBrainDecision.nextAction.status` 推导,不由 `mouth` 自行决定
|
||||
- 模型输出必须通过 `ctx.chatStructured`
|
||||
- prompt 必须通过 `buildMouthPromptContext`
|
||||
- `deliveryMarkdown` 为空时,必须使用 fallback builder 补足
|
||||
- `artifacts` 由 runtime 透传到 `result`,不是由 `mouth` 生成
|
||||
- `mouth` 不补编新事实,不扩张 artifacts
|
||||
- 对应数据结构:
|
||||
- `MouthOutput`
|
||||
- `HumanResult`
|
||||
- `HumanTaskOutput`
|
||||
- `AiArtifactRef`
|
||||
- 对应流转图:
|
||||
- `human` 阶段四:`mouth` 路径的数据流转
|
||||
|
||||
```ts
|
||||
function collectRuntimeArtifacts(existingArtifacts, toolResults) {
|
||||
const newArtifacts = extractArtifactsFromStructuredToolResults(toolResults)
|
||||
return [...existingArtifacts, ...newArtifacts]
|
||||
}
|
||||
```
|
||||
### 2.6 `human` 共享状态规则
|
||||
|
||||
实现要求:
|
||||
- `lastBrainDecision` 是当前分发快照,服务 `learning / hand / mouth` 读取当前动作。
|
||||
- `brainDecisions` 是最近决策历史,只保留最近 10 条,用于回看和审计。
|
||||
- `watchHint` 是轻量纠偏信号,不单独形成一个新节点。
|
||||
- `runtimeArtifacts` 只在 `hand` 阶段增长,在 `mouth` 阶段透传。
|
||||
- `trace.steps` 由各节点持续追加,供事件、审计与回放使用。
|
||||
|
||||
- `hand` 只执行 `brain` 已明确决定的工具调用。
|
||||
- `hand` 只写最近一轮工具结果、`runtimeArtifacts` 与必要的 `watchHint`。
|
||||
- `hand` 不直接生成最终结论。
|
||||
对应数据结构:
|
||||
|
||||
## 7. `mouth` 实现规则
|
||||
- `LastBrainDecision`
|
||||
- `BrainDecisionRecord`
|
||||
- `HumanState.watchHint`
|
||||
- `HumanState.runtimeArtifacts`
|
||||
- `HumanTraceStep`
|
||||
|
||||
```ts
|
||||
function runMouth(state, deliveryMode) {
|
||||
const input = {
|
||||
task: state.task,
|
||||
status: state.lastBrainDecision.nextAction.status,
|
||||
decision: state.lastBrainDecision.decision,
|
||||
facts: state.brainMemory.inside.facts,
|
||||
evidences: state.brainMemory.inside.evidences,
|
||||
openQuestions: state.brainMemory.inside.openQuestions,
|
||||
artifacts: state.runtimeArtifacts,
|
||||
deliveryMode
|
||||
}
|
||||
## 3. 跨层实现规则
|
||||
|
||||
return renderMouthOutput(input)
|
||||
}
|
||||
```
|
||||
### 3.1 `main` 与 `human` 的派发契约
|
||||
|
||||
实现要求:
|
||||
- `main` 与 `human` 的稳定边界是:
|
||||
- 输入:`HumanTaskInput + HumanRunDispatchOptions`
|
||||
- 输出:`HumanTaskOutput`
|
||||
- `main` 只调用 `HumanTaskRunner`,不直接读写 `HumanState`。
|
||||
- `human` runner 负责把最终 `HumanState` 收口为稳定的 `HumanTaskOutput`。
|
||||
|
||||
- `mouth` 只生成 `summary + deliveryMarkdown`。
|
||||
- `mouth` 不重新判断最终状态。
|
||||
- `mouth` 不补编新事实,不扩张 artifacts。
|
||||
- `result.status` 由运行时按 `nextAction.status` 回写。
|
||||
对应数据结构:
|
||||
|
||||
## 8. 共享实现规则
|
||||
- `HumanTaskInput`
|
||||
- `HumanRunDispatchOptions`
|
||||
- `HumanTaskOutput`
|
||||
|
||||
### 8.1 `deliveryMode`
|
||||
### 3.2 `deliveryMode`
|
||||
|
||||
```ts
|
||||
function resolveDeliveryMode(mainStage) {
|
||||
if (mainStage === 'main_direct_human_exec') return 'user_answer'
|
||||
if (mainStage === 'main_synthesis_human') return 'user_answer'
|
||||
if (mainStage === 'main_plan_exec') return 'task_handoff'
|
||||
return 'context_note'
|
||||
}
|
||||
```
|
||||
- `main_direct_human_exec` 与 `main_synthesis_human` 使用 `user_answer`
|
||||
- `main_plan_exec` 使用 `task_handoff`
|
||||
- `main_context_human` 使用 `context_note`
|
||||
- `deliveryMode` 只影响 `mouth` 的交付组织方式,不改变前置节点职责。
|
||||
|
||||
### 8.2 `contextEvidence` 提取
|
||||
对应数据结构:
|
||||
|
||||
```ts
|
||||
function extractContextEvidence(output) {
|
||||
const lines = extractBulletsFromSection(output.deliveryMarkdown, '可复用证据')
|
||||
- `HumanRunDispatchOptions.deliveryMode`
|
||||
- `HumanResult.deliveryMarkdown`
|
||||
|
||||
if (lines.length > 0) {
|
||||
return lines
|
||||
}
|
||||
### 3.3 `contextEvidence`
|
||||
|
||||
return output.summary ? [output.summary] : []
|
||||
}
|
||||
```
|
||||
- `contextEvidence` 是 `main` 侧消费的证据摘要,不直接保存整段 `context_note`。
|
||||
- 提取来源优先是 `context_note` 中的“可复用证据”区块。
|
||||
- 提取失败时,才 fallback 到 `HumanTaskOutput.summary`。
|
||||
- intent / routing 的判定理由不写入 `contextEvidence`。
|
||||
|
||||
实现要求:
|
||||
对应数据结构:
|
||||
|
||||
- 只从 `context_note` 的 `## 可复用证据` 区块提取 bullet / numbered line。
|
||||
- 提取失败时才 fallback 到 `summary`。
|
||||
- 路由理由不写入 `contextEvidence`。
|
||||
- `MainState.contextEvidence`
|
||||
- `HumanTaskOutput.summary`
|
||||
- `HumanTaskOutput.deliveryMarkdown`
|
||||
|
||||
### 8.3 `main` 派发 `human`
|
||||
### 3.4 `artifacts`
|
||||
|
||||
```ts
|
||||
function dispatchHuman(task, options) {
|
||||
const deliveryMode = options.deliveryMode ?? 'user_answer'
|
||||
const humanOutput = runHuman(task, { deliveryMode })
|
||||
- `artifacts` 的产生源头是工具结构化输出。
|
||||
- `hand` 负责把工具结果提取为 `runtimeArtifacts`。
|
||||
- `mouth` 只透传 `runtimeArtifacts` 到 `HumanResult`。
|
||||
- `main_plan_exec` 把子任务 artifacts 回写到 `MainPlanTask.artifacts`。
|
||||
- `main_synthesis_human` / `main_complete` 负责合并并收口到 `MainState.finalArtifacts`。
|
||||
|
||||
return {
|
||||
taskId: humanOutput.taskId,
|
||||
status: humanOutput.status,
|
||||
summary: humanOutput.summary,
|
||||
deliveryMarkdown: humanOutput.deliveryMarkdown,
|
||||
artifacts: humanOutput.artifacts ?? []
|
||||
}
|
||||
}
|
||||
```
|
||||
对应数据结构:
|
||||
|
||||
实现要求:
|
||||
- `AiArtifactRef`
|
||||
- `HumanState.runtimeArtifacts`
|
||||
- `HumanResult.artifacts`
|
||||
- `MainPlanTask.artifacts`
|
||||
- `MainState.finalArtifacts`
|
||||
|
||||
- `main` 与 `human` 的稳定交接边界是 `HumanTaskInput + HumanRunDispatchOptions -> HumanTaskOutput`。
|
||||
- `deliveryMode` 只作为运行时派发参数传入,不要求持久化到 `MainState`。
|
||||
- `main` 不直接改写 `human` 的内部状态。
|
||||
### 3.5 技术实现约束
|
||||
|
||||
### 8.4 run finalization
|
||||
- `main` 与 `human` 的运行编排必须基于 `@langchain/langgraph` 的 `StateGraph`。
|
||||
- graph 的状态 schema 必须通过各自目录下的 `state/schema.ts` 定义和约束。
|
||||
- graph 路由必须通过 `addConditionalEdges` 或等价 graph 路由表达,不要在 graph 外另写一套手工循环替代。
|
||||
- 模型结构化输出必须统一通过 `ctx.chatStructured`,由 runtime 封装的 `invokeStructuredChat` 承接。
|
||||
- 如果后续需要引入非结构化模型调用,也应先补 runtime 级封装,不在 node 内直接拼接底层模型调用。
|
||||
- 节点内部不要直接调用 `model.withStructuredOutput`,也不要散落手写 schema parse 逻辑。
|
||||
- prompt 必须通过 `build*Prompt` 或 `build*PromptContext` 组织,不在 node 内联堆大段 prompt 文本。
|
||||
- `learning`、`hand`、`complete` 这类确定性节点默认不引入额外模型调用。
|
||||
- 节点入口先执行取消检查,再进行模型调用、工具调用或状态写入。
|
||||
- 文档中的状态转移用于约束 graph 结构,不应用来反向实现一套脱离 LangGraph 的伪循环。
|
||||
|
||||
```ts
|
||||
function completeRun(finalOutput) {
|
||||
return {
|
||||
status: 'completed',
|
||||
finalDeliveryMarkdown: finalOutput.deliveryMarkdown,
|
||||
finalArtifacts: finalizeRunArtifacts(finalOutput.artifacts)
|
||||
}
|
||||
}
|
||||
```
|
||||
### 3.6 与数据文档的联动
|
||||
|
||||
实现要求:
|
||||
- `main` 侧实现时,先对照 `data-definition-and-flow.md` 中的:
|
||||
- `MainState`
|
||||
- `MainPlanTask`
|
||||
- `main` 三张流转图
|
||||
- `human` 侧实现时,先对照 `data-definition-and-flow.md` 中的:
|
||||
- `HumanState`
|
||||
- `brainMemory.inside`
|
||||
- `brainMemory.outside`
|
||||
- `lastBrainDecision` 与 `brainDecisions`
|
||||
- `lastParallelCallToolResults` 与 `result`
|
||||
- `human` 四张流转图
|
||||
- 跨层派发与交付时,重点对照:
|
||||
- `HumanTaskInput`
|
||||
- `HumanRunDispatchOptions`
|
||||
- `HumanTaskOutput`
|
||||
- `AiArtifactRef`
|
||||
|
||||
- `main_complete` 负责 run 级收尾。
|
||||
- run finalization 可以追加 run 级 artifacts。
|
||||
|
||||
## 9. 不变量
|
||||
## 4. 不变量
|
||||
|
||||
- `main` 只负责 run 级 orchestration。
|
||||
- `human` 只负责 task 级 autonomous execution。
|
||||
- `brain` 是唯一推理节点。
|
||||
- `deliveryMode` 只影响 `mouth` 的交付组织方式。
|
||||
- `brainMemory.inside` 只由 `brain` 通过 `memoryOps` 修改。
|
||||
- `lastBrainDecision` 是当前分发快照,`brainDecisions` 是最近决策历史。
|
||||
- `mouth` 不读取最近决策历史。
|
||||
- `brainMemory.inside` 只允许通过 `brain` 的 `memoryOps` 修改。
|
||||
- `lastBrainDecision` 只表示当前动作快照,`brainDecisions` 只表示最近历史。
|
||||
- `deliveryMode` 只影响交付组织,不改变求解职责。
|
||||
- `mouth` 不新增事实,不扩张 artifacts。
|
||||
- `main` 与 `human` 之间只通过稳定交接契约通信,不直接穿透内部状态。
|
||||
Reference in new issue
Block a user