[Feature] SOP 运行中向 Agent 节点插话:不打断地注入纠偏指令,支持排队到下一次尝试与一步打断续接

未关闭
#410 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
5/5
预计耗时
一周以上
新手友好度
25/100
Issue 类型
功能
描述清晰度
基本清楚
活跃度
活跃
技术栈
go, javascript, sql

调研方向

Start by reading the existing assistant steer flow in internal/domain/assistant_generation.go, internal/repo/assistant_generations.go, and internal/httpapi/assistant_generation.go, then compare it with run pause/resume in internal/engine/run_ops.go and node execution in internal/engine/node_execute.go. The issue calls for run-level steer and queue behavior across APIs, persistence, hooks, retries, and the UI; done means the listed delivery modes, status transitions, authorization and node-type checks, tests, and acceptance criteria all work.

由索引模型根据 Issue 内容生成。

描述

背景与借鉴来源

主流 AI coding agent 都把「Agent 正在干活时,人可以随时插一句话纠偏」当成基础交互,而且区分了两种投递方式:

  • openai/codex(https://github.com/openai/codex ,约 13.5 万 star,今天仍有提交):运行中发的消息分 Steer(在当前轮内尽快注入,不丢弃已做的工作)和 Queue(排队到当前轮结束后作为下一轮输入)。社区还在讨论「Background note / Soft steer」(openai/codex#55647),即只作为背景提示、不打断当前步骤。
  • anthropics/claude-code(https://github.com/anthropics/claude-code ,约 15.5 万 star,今天仍有提交):Agent 工作中输入的消息会在下一个工具调用边界注入;hooks 的 PostToolUse / UserPromptSubmit 可以通过 hookSpecificOutput.additionalContext 把额外上下文送进模型。社区诉求见 #98493(排队而不只是 steer)、#73661(排队消息按 FIFO 逐条处理,而不是合并成一条)。
  • OpenHands/OpenHands(https://github.com/OpenHands/OpenHands ,约 9.3 万 star,活跃)、cline/cline(约 7.2 万 star,活跃):Agent 运行中的用户消息进入事件流,在下一步推理前被读到,不需要先停止任务。

这些项目的共同点:插话不等于暂停。插话不杀进程、不丢上下文,在安全边界(工具调用之间或一轮结束时)投递;投递状态(待投递 / 已送达 / 已被 Agent 确认)对人可见。

LoopForge 现状与不足

LoopForge 已经在助手会话里实现了 steer,但 SOP 运行里的 Agent 节点没有对应能力:

  1. 助手会话已有 steer:internal/domain/assistant_generation.go 的 AssistantSteer,internal/repo/assistant_generations.go 的 SteerAssistantGeneration,internal/httpapi/assistant_generation.go 的 followSteer,前端在 web/workbench.js 的 steer()。
  2. SOP 运行只有「暂停 → 补充上下文 → 继续」:POST /api/runs/:id/pause → Engine.Pause(internal/engine/run_ops.go)会 cancelRunnerTasksForRun 并 Interrupt 取消本地调度,直接杀掉正在跑的 Agent CLI;之后要再调 POST /api/runs/:id/resume,由 resumePaused 写入 recordHumanContext、用 ResumeSessionID 加 --resume(internal/runtime/provider_claude.go / provider_codex.go),提示词变成「请从刚才中断的位置继续执行」(internal/engine/node_execute.go)。
    • 想纠正方向必须先打断、等节点停稳、再点一次继续,至少两步。
    • 打断时 CLI 可能正改到一半文件、正跑一半测试,还要重新起沙箱进程、重建 MCP,长任务成本很高。
    • 子 SOP 要级联暂停和恢复(childRunIDs),一句提示的代价很大。
  3. 人工上下文只在节点启动时读一次:writeHumanContextFile 只在 node_execute.go 准备沙箱时写 human-context.md;节点运行期间人补充的信息没有通道送进正在跑的 CLI。
  4. 没有排队语义:节点运行中,人不能先留言、等这一轮完成后再作为下一轮(返工 / 下一次尝试)的输入;只能等节点失败或进入门禁再写。
  5. 平台已经给各 CLI 装了 hooks(internal/observability/hooks.go 的 PostToolUse/UserPromptSubmit,internal/runtime/hooks.go 的 writeHookSettings/mergeHookSettings),但现在只用来做观测落盘(hooks.ndjson),没有用来回写上下文。

建议方案

1. 数据模型与 API
  • 新增运行级消息队列(归属 RunsRepo,新建 run_steers 表或 Run 的子资源),字段:id, run_id, node_id(可空=当前活跃 Agent 节点), mode(steer|queue), content, actor_id, status(pending|delivered|acknowledged|expired|cancelled), delivered_attempt, delivered_via(hook|resume|next_attempt), created_at, delivered_at。按 AGENTS.md 约定,migrations/ 下补一份 SQL 脚本。
  • POST /api/runs/:id/steer(runsHandlers,复用 project_write_access.go 的写权限校验),请求体 {nodeId?, mode, content};DELETE /api/runs/:id/steer/:steerId 撤回未投递的消息;GET 在运行详情里返回队列。
  • 写入运行事件(e.event(..., "run.steer.queued" / "run.steer.delivered"))和 appendControlTelemetry,审计里能看到谁在哪个节点第几次尝试插了什么话。
2. 投递(按执行器能力分级,从无损到有损)
  • A. 工具调用边界注入(首选,不打断):对支持 hooks 回写上下文的 CLI(Claude Code / CodeBuddy 的 PostToolUse 与 UserPromptSubmit),在 hook.py 旁加一个轻量命令:读取工作区下 .agentflow/steer/inbox.jsonl 中 pending 的消息,以 hookSpecificOutput.additionalContext 输出,并把已投递的 id 写到 outbox。引擎(本地调度或 runner daemon)负责把队列同步进 inbox,并从 outbox/hooks.ndjson 回收投递状态。
  • B. 回合结束后续接:不支持 A 的执行器(Codex exec --json、OpenCode 等),mode=steer 退化为「当前 CLI 进程结束后立刻以 --resume <sessionID> 再起一轮,提示词只包含插话内容」,节点保持 running,不经过 waiting_approval,也不计入 MaxAttempts。
  • C. 立即打断(显式选择):前端提供「打断并插话」,内部等价于 Pause + ResumePaused(humanContext) 一步完成,复用现有 ResumeSessionID 逻辑,不需要人点两次。
  • mode=queue:不在当前尝试里投递,挂到该节点下一次尝试(门禁 rework、targeted_fix 重试、或人工 Retry)的 Feedback 中,追加在现有 FeedbackPrompt 后面;多条按 FIFO 逐条列出,不合并改写。
3. 前端
  • 运行详情页的活跃 Agent 节点卡片加输入框,支持「插话 / 排队 / 打断并插话」三种发送方式(交互参考 web/workbench.js 现有 steer());消息气泡显示投递状态,pending 状态可撤回。
4. 边界
  • 只对 type=agent 节点生效;script / approval 节点直接拒绝(400);子 SOP 节点按 ChildRunID 转给子运行当前活跃的 Agent 节点。
  • 单条长度沿用 maxHumanContextBytes(8000);同一节点 pending 上限(例如 20 条)。
  • 节点在投递前结束(成功 / 失败 / 进入门禁):steer 自动转为 queue 留给下一次尝试,或标为 expired 并提示,不静默丢弃。
  • 插话内容同样写入 human-context 产物,保证事后可追溯;gitwork/exclude.go 中排除 .agentflow/steer/,不进入交付分支。

验收标准

  1. 在 Claude Code / CodeBuddy 执行器的 Agent 节点运行期间调用 POST /api/runs/:id/steer(mode=steer),CLI 进程不重启(同一 invocation id、无 run.paused/run.resumed 事件),下一次工具调用后模型输出能体现插话内容;消息状态从 pending → delivered,运行事件里有 run.steer.delivered。
  2. 在 Codex 执行器上同样操作,当前轮结束后自动 --resume 续接,节点不进入 waiting_approval,Attempt 不加 1。
  3. mode=queue 的多条消息在节点下一次尝试时按 FIFO 逐条出现在提示词里,当前尝试中不投递。
  4. 「打断并插话」一步完成暂停加继续,行为等同于现在先 pause 再 resume 带 context 的组合,子 SOP 级联正确。
  5. 节点在消息投递前结束时,消息被转为 queue 或标为 expired,界面可见,不会静默丢失;pending 消息可撤回。
  6. 对 script / approval 节点插话返回 400;无项目写权限返回 403。
  7. 单元测试覆盖:队列状态机、hook 注入命令的读取与回执、三种降级路径、节点结束时的转存逻辑;e2e 覆盖运行详情页发送插话并看到状态变化。
  8. .agentflow/steer/ 不出现在交付分支的 diff 中。
主要语言
Go
星标
141
派生
28
平均合并
1 天 15 小时
30 天内合并 PR
113

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

Tencent/LoopForge 的其他 Issue

查看 Tencent/LoopForge 的全部 Issue

相似的 Issue

更多 Go Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。