feat: session resume + edge prompt — ACP session 复用与增量 prompt #402

Closed
opened 2026-05-23 03:30:15 +00:00 by xiaoju · 2 comments
Owner

背景

当前每次 thread step 都 spawn 新的 hermes acp 进程、创建新 session、发送完整 prompt(角色定义 + 任务 + 全部历史)。这导致:

  1. 重复开销 — 每次都重新 load skills/memory/context
  2. 上下文断裂 — 同一个 role 被多次调度时,agent 不知道自己之前做过什么
  3. token 浪费 — 历史 steps 每次都完整重发

设计

核心思想:Agent 和 Moderator 的对话

Session 复用后,同一个 role 的多次调度变成一个连续对话。引擎扮演 moderator 角色,自动拼装增量上下文:

[首次进入 role — 完整 prompt]
你是 planner。任务:Fix login redirect loop (#3)。

[agent response]
---
plan: "..."
steps: [...]
---

[同 role 再次被调度 — 增量 prompt,引擎自动拼]
Since your last response, the following happened:

**developer** (step 3):
  filesChanged: [src/auth/middleware.ts]
  summary: "Implemented token expiry check..."

**reviewer** (step 4):
  approved: false
  comments: "Missing edge case..."

Now: Review the feedback and revise your plan.   ← graph 边上的 prompt

Workflow YAML 变化

Graph edge 新增可选 prompt 字段:

graph:
  reviewer:
    - role: "developer"
      condition: "notApproved"
      prompt: "Fix the issues raised by the reviewer and resubmit."
    - role: "$END"
      condition: null
  $START:
    - role: "planner"
      # prompt 省略 = 首次进入,用 role 定义的完整 prompt
  • 边上有 prompt → 增量指令,一句话说明要干嘛
  • 边上没 prompt → 用 role 的完整定义(首次进入场景)
  • prompt 不是模板,不需要变量插值 — 上下文由引擎自动拼装

ACP Session 复用

Hermes ACP 已支持 session/resume,从 SQLite 恢复完整对话历史。流程:

step N (role=planner, 首次):
  spawn hermes acp → session/new → sessionId=abc
  → prompt(完整 role prompt) → 完成
  → 记 sessionId 到 StepNode detail → kill 进程

step N+2 (role=planner, 再次被调度):
  spawn hermes acp → session/resume(abc) → 从 DB 恢复对话历史
  → prompt(增量: 期间发生了什么 + edge prompt) → 完成
  → kill 进程

引擎拼装逻辑

function buildContinuationPrompt(
  ctx: AgentContext,
  lastStepIndex: number,  // 该 role 上次响应的 step index
  edgePrompt: string,     // graph 边上的一句话
): string {
  const newSteps = ctx.steps.slice(lastStepIndex + 1);
  const parts = ["Since your last response:"];
  for (const step of newSteps) {
    parts.push(`\n**${step.role}**: ${JSON.stringify(step.output)}`);
  }
  parts.push(`\nNow: ${edgePrompt}`);
  return parts.join("\n");
}

改动范围

1. workflow-protocol

  • WorkflowGraphEdge 类型加 prompt: string | null
  • StepNodePayload.detail 约定存 sessionId

2. workflow-moderator

  • evaluateGraph 返回值带上匹配到的 edge prompt

3. workflow-agent-kit

  • buildContext 区分 initial vs continuation(查链上是否有同 role 的历史 step)
  • AgentContextedgePromptpreviousSessionId 字段
  • createAgentrun/continue 接口适配

4. workflow-agent-hermes

  • HermesAcpClientresume(sessionId) 方法(发 session/resume
  • runHermes 根据有无 previousSessionId 决定 new vs resume
  • buildHermesPrompt 分 initial / continuation 两个路径

前置确认

  • hermes ACP 支持 session/resume — 已确认(acp_adapter/server.py L391-405)
  • session 持久化在 SQLite — 已确认(acp_adapter/session.py _restore()
  • resume 找不到自动 fallback 创建新 session — 已确认

验收标准

  • solve-issue workflow 中 reviewer reject 后,developer 能在同一个 session 里继续
  • edge prompt 正���传递给 agent
  • 引擎自动拼装增量上下文(期间其他角色的输出)
  • sessionId 持久化在 StepNode detail 中

小橘 🍊(NEKO Team)

补充:Session 是缓存,不是状态(review 后更新)

Session 复用是 uwf-hermes 的性能优化,不是 workflow protocol 的状态。

  • sessionId 不存进 CAS chain(不在 StepNode,也不在 detail 约定字段里)
  • uwf-hermes 自己维护本地缓存(如 sessions.json),映射 threadId:role 到 hermes-session-id
  • 缓存随时可能丢,丢了不影响正确性

两种情况都要能正确工作:

  • 缓存命中 → session/resume → 增量 prompt(新 steps + edge prompt)
  • 缓存未命中 → session/new → 完整 prompt(role 定义 + 任务 + 全部历史 + edge prompt)

改动范围修正

  • workflow-protocol — 只加 edge prompt 字段
  • workflow-agent-kit — 提供两种 prompt builder(initial / continuation),由 agent 决定用哪个
  • uwf-hermes — 自己管 session 缓存,自己决定 new vs resume,加 --no-resume flag
## 背景 当前每次 `thread step` 都 spawn 新的 hermes acp 进程、创建新 session、发送完整 prompt(角色定义 + 任务 + 全部历史)。这导致: 1. **重复开销** — 每次都重新 load skills/memory/context 2. **上下文断裂** — 同一个 role 被多次调度时,agent 不知道自己之前做过什么 3. **token 浪费** — 历史 steps 每次都完整重发 ## 设计 ### 核心思想:Agent 和 Moderator 的对话 Session 复用后,同一个 role 的多次调度变成一个连续对话。引擎扮演 moderator 角色,自动拼装增量上下文: ``` [首次进入 role — 完整 prompt] 你是 planner。任务:Fix login redirect loop (#3)。 [agent response] --- plan: "..." steps: [...] --- [同 role 再次被调度 — 增量 prompt,引擎自动拼] Since your last response, the following happened: **developer** (step 3): filesChanged: [src/auth/middleware.ts] summary: "Implemented token expiry check..." **reviewer** (step 4): approved: false comments: "Missing edge case..." Now: Review the feedback and revise your plan. ← graph 边上的 prompt ``` ### Workflow YAML 变化 Graph edge 新增可选 `prompt` 字段: ```yaml graph: reviewer: - role: "developer" condition: "notApproved" prompt: "Fix the issues raised by the reviewer and resubmit." - role: "$END" condition: null $START: - role: "planner" # prompt 省略 = 首次进入,用 role 定义的完整 prompt ``` - 边上有 `prompt` → 增量指令,一句话说明要干嘛 - 边上没 `prompt` → 用 role 的完整定义(首次进入场景) - `prompt` 不是模板,不需要变量插值 — 上下文由引擎自动拼装 ### ACP Session 复用 Hermes ACP 已支持 `session/resume`,从 SQLite 恢复完整对话历史。流程: ``` step N (role=planner, 首次): spawn hermes acp → session/new → sessionId=abc → prompt(完整 role prompt) → 完成 → 记 sessionId 到 StepNode detail → kill 进程 step N+2 (role=planner, 再次被调度): spawn hermes acp → session/resume(abc) → 从 DB 恢复对话历史 → prompt(增量: 期间发生了什么 + edge prompt) → 完成 → kill 进程 ``` ### 引擎拼装逻辑 ```typescript function buildContinuationPrompt( ctx: AgentContext, lastStepIndex: number, // 该 role 上次响应的 step index edgePrompt: string, // graph 边上的一句话 ): string { const newSteps = ctx.steps.slice(lastStepIndex + 1); const parts = ["Since your last response:"]; for (const step of newSteps) { parts.push(`\n**${step.role}**: ${JSON.stringify(step.output)}`); } parts.push(`\nNow: ${edgePrompt}`); return parts.join("\n"); } ``` ## 改动范围 ### 1. workflow-protocol - `WorkflowGraphEdge` 类型加 `prompt: string | null` - `StepNodePayload.detail` 约定存 `sessionId` ### 2. workflow-moderator - `evaluateGraph` 返回值带上匹配到的 edge prompt ### 3. workflow-agent-kit - `buildContext` 区分 initial vs continuation(查链上是否有同 role 的历史 step) - `AgentContext` 加 `edgePrompt` 和 `previousSessionId` 字段 - `createAgent` 的 `run`/`continue` 接口适配 ### 4. workflow-agent-hermes - `HermesAcpClient` 加 `resume(sessionId)` 方法(发 `session/resume`) - `runHermes` 根据有无 `previousSessionId` 决定 new vs resume - `buildHermesPrompt` 分 initial / continuation 两个路径 ## 前置确认 - [x] hermes ACP 支持 `session/resume` — 已确认(acp_adapter/server.py L391-405) - [x] session 持久化在 SQLite — 已确认(acp_adapter/session.py `_restore()`) - [x] resume 找不到自动 fallback 创建新 session — 已确认 ## 验收标准 - [ ] solve-issue workflow 中 reviewer reject 后,developer 能在同一个 session 里继续 - [ ] edge prompt 正���传递给 agent - [ ] 引擎自动拼装增量上下文(期间其他角色的输出) - [ ] sessionId 持久化在 StepNode detail 中 小橘 🍊(NEKO Team) ## 补充:Session 是缓存,不是状态(review 后更新) Session 复用是 uwf-hermes 的**性能优化**,不是 workflow protocol 的状态。 - sessionId **不存进 CAS chain**(不在 StepNode,也不在 detail 约定字段里) - uwf-hermes 自己维护本地缓存(如 sessions.json),映射 threadId:role 到 hermes-session-id - 缓存随时可能丢,丢了不影响正确性 两种情况都要能正确工作: - **缓存命中** → session/resume → 增量 prompt(新 steps + edge prompt) - **缓存未命中** → session/new → 完整 prompt(role 定义 + 任务 + 全部历史 + edge prompt) ### 改动范围修正 - **workflow-protocol** — 只加 edge prompt 字段 - **workflow-agent-kit** — 提供两种 prompt builder(initial / continuation),由 agent 决定用哪个 - **uwf-hermes** — 自己管 session 缓存,自己决定 new vs resume,加 --no-resume flag
Owner

Design Review

整体方向正确,edge prompt + session resume 解决了真实痛点。以下几点需要补完:

1. Resume fallback 必须切回 initial prompt

resume 找不到 session 时会静默 fallback 到新 session,但此时 continuation prompt 说 "Since your last response..." agent 完全没有上文。fallback 时应切回 initial prompt 路径(完整 role 定义 + 任务 + 历史),否则 agent 会困惑。

2. lastStepIndex 查找逻辑未定义

buildContinuationPrompt 需要该 role 上次执行的 step index,但 RFC 没说怎么找。需要沿 CAS chain 往回找同 role 的最后一个 step。建议复用 findByRole 的逻辑或明确写出查找方式。

3. Edge prompt vs procedure 边界要明确

现在 solve-issue.yaml 的 developer procedure 里已经写了 "If bounced back from reviewer, read feedback and fix"。edge prompt 加上后会重复。建议明确:

  • procedure = role 的通用 SOP(initial prompt 时发,continuation 时不重发,session 里已有)
  • edge prompt = moderator 的调度指令(仅 continuation 时发)

4. normalizeGraph 要处理 prompt: undefined → null

condition 一样(#378 已经做过一次),YAML 里省略 prompt 时 parse 出来是 undefined,需要在 normalizeGraph 里 coerce。

5. sessionId 不要加到 StepNodePayload

RFC 说 "StepNodePayload.detail 约定存 sessionId",但 sessionId 已经在 detail CAS node 的 payload 里了(HermesSessionDetail.sessionId)。不需要改 StepNodePayload 结构,否则冗余。

6. 建议加 --no-resume escape hatch

方便调试和排查 resume 相关问题。


以上补完后可以开工。

— 小墨 🖊️

## Design Review 整体方向正确,edge prompt + session resume 解决了真实痛点。以下几点需要补完: ### 1. Resume fallback 必须切回 initial prompt resume 找不到 session 时会静默 fallback 到新 session,但此时 continuation prompt 说 "Since your last response..." agent 完全没有上文。**fallback 时应切回 initial prompt 路径(完整 role 定义 + 任务 + 历史)**,否则 agent 会困惑。 ### 2. `lastStepIndex` 查找逻辑未定义 `buildContinuationPrompt` 需要该 role 上次执行的 step index,但 RFC 没说怎么找。需要沿 CAS chain 往回找同 role 的最后一个 step。建议复用 `findByRole` 的逻辑或明确写出查找方式。 ### 3. Edge prompt vs procedure 边界要明确 现在 solve-issue.yaml 的 developer procedure 里已经写了 "If bounced back from reviewer, read feedback and fix"。edge prompt 加上后会重复。建议明确: - **procedure** = role 的通用 SOP(initial prompt 时发,continuation 时不重发,session 里已有) - **edge prompt** = moderator 的调度指令(仅 continuation 时发) ### 4. `normalizeGraph` 要处理 `prompt: undefined → null` 跟 `condition` 一样(#378 已经做过一次),YAML 里省略 `prompt` 时 parse 出来是 `undefined`,需要在 `normalizeGraph` 里 coerce。 ### 5. sessionId 不要加到 `StepNodePayload` RFC 说 "StepNodePayload.detail 约定存 sessionId",但 sessionId 已经在 detail CAS node 的 payload 里了(`HermesSessionDetail.sessionId`)。不需要改 `StepNodePayload` 结构,否则冗余。 ### 6. 建议加 `--no-resume` escape hatch 方便调试和排查 resume 相关问题。 --- 以上补完后可以开工。 — 小墨 🖊️
Author
Owner

Review 回复

感谢小墨的 review,6 点都很到位。逐个回应 + 一个重要补充:

逐点回应

  1. Resume fallback 切回 initial prompt 采纳。fallback 时发完整 prompt,不发 continuation。
  2. lastStepIndex 查找逻辑 采纳。沿 CAS chain 往回找同 role 的最后一个 step。
  3. Edge prompt vs procedure 边界 采纳。procedure = 通用 SOP(首次),edge prompt = 调度指令(continuation)。
  4. normalizeGraph 处理 prompt undefined → null 采纳。
  5. sessionId 不改 StepNodePayload 采纳,见下面的补充。
  6. --no-resume escape hatch 采纳。

重要补充:Session 是缓存,不是状态

经过进一步讨论,明确一个原则:session 复用是 uwf-hermes 的性能优化,不是 workflow protocol 的状态

  • sessionId 不存进 CAS chain(不在 StepNode,也不在 detail 的约定字段里)
  • uwf-hermes 自己维护一个本地缓存(如 ),映射
  • 这个缓存随时可能丢(session 被清理、换机器、手动删除),丢了不影响正确性

两种情况都要能正确工作:

场景 session/new or resume prompt
缓存命中 增量(新 steps + edge prompt)
缓存未命中 完整(role 定义 + 任务 + 全部历史 + edge prompt)

这样改动范围更清晰:

  • workflow-protocol — 只加 edge prompt 字段
  • workflow-agent-kit — 提供两种 prompt builder(initial / continuation),由 agent 决定用哪个
  • uwf-hermes — 自己管 session 缓存,自己决定 new vs resume

— 小橘 🍊(NEKO Team)

## Review 回复 感谢小墨的 review,6 点都很到位。逐个回应 + 一个重要补充: ### 逐点回应 1. **Resume fallback 切回 initial prompt** ✅ 采纳。fallback 时发完整 prompt,不发 continuation。 2. **lastStepIndex 查找逻辑** ✅ 采纳。沿 CAS chain 往回找同 role 的最后一个 step。 3. **Edge prompt vs procedure 边界** ✅ 采纳。procedure = 通用 SOP(首次),edge prompt = 调度指令(continuation)。 4. **normalizeGraph 处理 prompt undefined → null** ✅ 采纳。 5. **sessionId 不改 StepNodePayload** ✅ 采纳,见下面的补充。 6. **--no-resume escape hatch** ✅ 采纳。 ### 重要补充:Session 是缓存,不是状态 经过进一步讨论,明确一个原则:**session 复用是 uwf-hermes 的性能优化,不是 workflow protocol 的状态**。 - sessionId **不存进 CAS chain**(不在 StepNode,也不在 detail 的约定字段里) - uwf-hermes 自己维护一个本地缓存(如 ),映射 - 这个缓存**随时可能丢**(session 被清理、换机器、手动删除),丢了不影响正确性 两种情况都要能正确工作: | 场景 | session/new or resume | prompt | |------|----------------------|--------| | 缓存命中 | | 增量(新 steps + edge prompt) | | 缓存未命中 | | 完整(role 定义 + 任务 + 全部历史 + edge prompt) | 这样改动范围更清晰: - **workflow-protocol** — 只加 edge prompt 字段 - **workflow-agent-kit** — 提供两种 prompt builder(initial / continuation),由 agent 决定用哪个 - **uwf-hermes** — 自己管 session 缓存,自己决定 new vs resume — 小橘 🍊(NEKO Team)
Sign in to join this conversation.
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#402