🔥 Hermes OpenAgent Dashboard

Docs > docs/orchestration-tracker-design.md

📄 Orchestration Tracker — 设计文档

Orchestration Tracker — 设计文档

任务: t_02222bb4 — hermes-dashboard 实时多角色编排 SSE 显示 目标: 不修改 opencode/oh-my-openagent 本身, 通过观察其运行时副作用文件推断 Sisyphus 多阶段编排状态 (EXPLORE → PLAN → ROUTE → EXECUTE_OR_SUPERVISE → VERIFY → RETRY → DONE), 并通过 SSE 实时推送到 dashboard。

1. Sisyphus 副作用文件清单

调研方法: explore agent 实际 ls -laR + stat + 解析了 /home/yi/.hermes/kanban/workspaces/*/.omo/ 下所有 JSON; 另一 explore agent 通读了 oh-my-openagent@latest/dist/index.js (177,998 行打包 bundle) 中全部 writeFileSync/appendFileSync 调用点; librarian agent 核实了 hook 机制与 opencode session 存储。

1.1 核心结论

不存在显式的 phase 状态机。 "EXPLORE"/"PLAN"/"ROUTE"/"VERIFY" 等字符串在 dist bundle 中只出现在 prompt 模板里 (如 "PHASE 1: ANALYZE THE DESIGN SYSTEM"), 没有任何运行时代码把它们作为枚举值写入文件或事件。Phase 是 Sisyphus 的"叙事心智模型", 必须由外部观察者推断

1.2 运行时实际写入的文件 (按信号价值排序)

路径 写入时机 内容 信号类型
<project>/.omo/boulder.json 每次状态迁移 (sub-agent 启动/任务开始/任务完成) 原子重写 {schema_version:2, active_work_id, works:{<id>:{status, active_plan, plan_name, session_ids[], task_sessions:{<taskKey>:{session_id, agent, status, started_at, ended_at}}}}} 主状态 — work/task 级
<project>/.omo/plans/<plan-name>.md Prometheus/Sisyphus 勾选 checkbox 时 ## TODOs- [x] 1. … / - [ ] 2. …; ## Final Verification Wave- [ ] F1. … phase 进度 — checkbox 驱动
<project>/.omo/run-continuation/<sesID>.json session 活跃期间反复重写 (实测 30s~10min 间隔) {sessionID, updatedAt, sources:{"background-task":{state:"active"\|"idle", reason:"N background task(s) active"}}} 活跃信号 — 263B=active / 214B=idle tombstone
~/.local/share/opencode/storage/oh-my-openagent/tui-state/<hash>.json TUI session 状态变化 {projectDir, activeAgents:[{name,status}], jobBoard:[{title,status,toolCalls,lastTool}]} agent 占用 — projectDir 字段可解 t_↔ses_ 映射
~/.local/share/opencode*/opencode.db (SQLite, 每个 hermes profile 各一个) 每次 message/part/event 表: session(id,parent_id,agent,title,directory,time_created,time_updated), message(session_id,data), part(session_id,data), event(aggregate_id,seq,type,data), todo(session_id,content,status,priority) 最细粒度 — tool call 级
<project>/.omo/notepads/<plan>/{learnings,decisions,issues,problems}.md sub-agent 完成任务后追加 markdown 追加 辅助 — mtime 增量
~/.omo/runtime/<teamRunId>/state.json team-mode 专用 (本任务不用) team 状态机 不适用

1.3 关键实测证据

  • kanban workspaces: 7 个 workspace 中只有 3 个有 .omo/, 且每个只有 run-continuation/ses_*.json 一个文件, 全部是 Birth==Modify==Change (精确到毫秒) 的 idle tombstone。.omo/plans/.omo/state/.omo/todos/.omo/sessions/ 在任何地方都不存在
  • run-continuation 双变体: 214B={"state":"idle"} (session 结束一次性写入); 263B={"state":"active","reason":"N background task(s) active"} (活跃期间反复重写, hermes-dashboard/.omo/ 下实测 10min 与 30s 重写间隔)。
  • opencode.db 多 profile: hermes worker 跑在隔离 profile (~/.hermes/profiles/omo-pm/home/.local/share/opencode/opencode.db), 主用户 DB (~/.local/share/opencode/opencode.db) 只含主会话。必须聚合所有 profile DB, 单看默认 DB 会漏掉 worker 的 sub-agent 会话。
  • parent_id 在 worker profile DB 中有效: sub-agent session 的 parent_id 指向 root session (实测 ses_ff045758... parent=ses_ff046dbf...), 可构建 orchestration tree。
  • hook 机制不可外部观察: team keyword-detector 等 hook 是 opencode 进程内 JS 回调 (chat.message/tool.execute.before/event 等事件), 不写文件、不发 IPC。外部进程只能读 SQLite event 表与 part 表。

2. Phase 推断策略

信号源优先级 (新信号覆盖旧信号):

2.1 任务→会话定位 (三级回退)

  1. run-continuation 链接: workspaces/<task_id>/.omo/run-continuation/ses_*.json 文件名即 root session id (+其 parent_id 子会话)。
  2. task-id 提及匹配: 任务窗口 (started_at±120s) 内的 root session, 若其前 20 个 part 的 JSON data 中包含 t_<id> 字符串 (hermes worker 会把任务 spec 作为首条 prompt 传给 opencode) → 认定归属。实测有效: 本会话首 part 含完整任务 spec。
  3. 目录+窗口回退: 若以上皆无, 取窗口内 directory 匹配 workspace 的 root session。

2.2 part 分类 → phase 映射 (_classify_part)

读取最活跃 session 的最近 40 个 part (按 time_created DESC), 首个携带 phase 信号的 part 决定当前 phase:

part 特征 推断 phase
type=patchtool∈{write,edit} EXECUTE_OR_SUPERVISE
tool=tasksubagent_type∈{explore,librarian} EXPLORE
tool=tasksubagent_type∈{plan,metis,momus}tool=todowrite PLAN
tool=task 其他 (category 派发) 或 tool=delegate_task EXECUTE_OR_SUPERVISE
tool∈{read,grep,glob,webfetch,websearch,context7_*} EXPLORE
tool=lsp_diagnosticstool=bash 且命令含 pytest/npm test/deploy.sh//healthz/curl VERIFY
tool=bashstate.status=error RETRY
kanban status∈{done,blocked,failed} DONE
无信号但 root session 活跃且 agent 含 "Sisyphus" PLAN (fallback)
无任何 session EXPLORE (cold start, "no session found yet")

2.3 未来增强 (未实现, 已验证可行性)

  • boulder.json checkbox 驱动: 解析 .omo/plans/<plan>.md## TODOs / ## Final Verification Wave 的 checkbox 状态, 首个未勾选序号决定 EXECUTE vs VERIFY 边界 (dist/index.js:96339 parseCurrentTopLevelTask 的 canonical 算法)。当前环境中 kanban workspace 不产生 boulder.json (worker 不走 /start-work), 故未采用。
  • event 表 delta 轮询: event 表有单调 seq, 可按 aggregate_id (session) + seq 做增量轮询, 比全量扫 part 表更高效。当前 1s 轮询 part 表已足够 (单会话 40 行 LIMIT)。
  • stale 检测: session time_updated 超过 300s 无更新 → payload 标记 stale:true (UI 显示灰化提示)。

3. Tracker 架构设计

3.1 组件

opencode profile DBs (只读, mode=ro)          kanban.db (只读)
  ├─ ~/.local/share/opencode/opencode.db        └─ tasks(started_at, status)
  └─ ~/.hermes/profiles/*/home/.local/share/opencode/opencode.db
              │
              ▼  每秒轮询 (SSE generator 驱动)
  services/orchestration_tracker.py
    ├─ _sessions_for_task()   三级回退定位 session
    ├─ _classify_part()       part→phase 映射
    ├─ _infer_current_phase() 最活跃 session 的最新信号
    ├─ record_event()         phase/agent 变化时写入 orchestration.db
    ├─ get_current_phase()    公开 API: {task_id, phase, started_at, elapsed, agent, files[], status, detail, stale, ts}
    └─ get_full_timeline()    公开 API: 持久化历史 (oldest first)
              │
              ▼
  orchestration.db — orchestration_events 表
    (id, task_id, phase, agent, files(json), detail, started_at, elapsed, created_at)
    INSERT 策略: 仅当 (phase, agent) 与上一行不同时插入 (去抖)
              │
              ▼
  routes/api.py
    ├─ GET /api/tasks/<id>/orchestration/stream   SSE, 1s 心跳, data:{json}\n\n, 终态自动断流, 30min 上限
    └─ GET /api/tasks/<id>/orchestration/timeline 一次性 {current, timeline[]}
              │
              ▼
  前端 (templates + static/js/main.js)
    ├─ task_detail.html "🎛️ 编排实时状态" 卡片   EventSource 实时渲染 phase/agent/elapsed/files
    ├─ task_detail.html "🕓 完整编排时间线" 区块  展开时 fetch timeline, 流结束时自动刷新
    ├─ dashboard.html kanban 计数条 #running-phase + 每个 running 卡片 .running-phase 指示器 (10s 轮询)
    └─ base.html .phase-badge.phase-* CSS (EXPLORE/PLAN/VERIFY/RETRY/DONE 配色 + pulse 动画)

3.2 设计决策与理由

  1. 轮询驱动而非 inotify: SSE generator 本身每秒醒来一次, 顺带调 get_current_phase() — 无需独立 watcher 进程, 无 inotify 跨 profile 目录监听的复杂度。SQLite 只读查询 (40 行 LIMIT) 在 1s 周期内开销可忽略。
  2. 多 profile DB 聚合: _opencode_db_paths() 枚举 ~/.hermes/profiles/*/home/.local/share/opencode/opencode.db, 对每个 DB 独立定位+推断, last_activity 最新者胜。这是实测发现: worker (omo-pm profile) 的会话不在默认 DB 中。
  3. 读模式 mode=ro + URI 打开: 绝不触碰活跃进程的 WAL。
  4. 持久化与实时分离: 实时态每次重新推断 (无状态); 持久化仅在 (phase, agent) 变化时写一行 — task 跑完后 timeline 自然保留 ≥2 条历史, 满足验收 #5。
  5. 降级链永不抛异常: task 不存在 → {error:"task not found"}; DB 不可读 → EXPLORE+"opencode.db unavailable"; 无 session → EXPLORE+"no session found yet"。SSE 流对 service 异常免疫 (捕获后照常 yield)。
  6. 前端去抖: EventSource onmessage 按 payload 字符串去重 (1s 心跳大部分是重复 payload), 仅变化时重渲染; 终态 (status∈{done,blocked,failed}) 收到最后一帧后 es.close() 并刷新 timeline。

← Back to Docs

⚙️ Running Processes

Loading…