Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ Full subagent reference (design, wire protocol, Web panel, TUI bridging, test ma

Sub-agent sessions are **hidden from normal session lists** and surfaced only in the parent session's Web「后台任务」panel: the child meta persists `parent_session_id` (survives daemon restart; resume restores `subagent_depth=1` so the depth limit keeps holding), `GET /api/sessions` excludes them by default, `?parent=<id>` queries them, and `DELETE /api/sessions/:id?purge=1` is the panel's「清除」(destroy + delete disk data; sub-agent sessions only, busy → 409). Frontend: `web/src/lib/subagentTasks.js` (pure state, Node tests) + `lib/useSubagentTasks.js` (REST list + WS increments + retainSession for running children so their permission/question requests reach App-level listeners even with the panel closed) + `components/SubagentPanel.jsx` (overlay inside ChatView's message area: running/settled card groups, per-card stop, read-only transcript that shares the main conversation's `projectCollapsedTranscriptItems` projection and `TranscriptItems` renderer, with AskUserQuestion rows filtered out through the projection seam). A running child's `permission_request` pops the global PermissionModal and its `question_request` renders in the parent's QuestionPicker (payloads carry the child `session_id`, so answers route back automatically); both show a「来自后台任务:<title>」origin label.

**蜂群模式(网状)(openspec add-mesh-swarm-mode,复刻 Codex Multi-Agent V2,全文见 [docs/subagents.md](docs/subagents.md) §9)。** 蜂群模式是会话级三态 `off | star | mesh`(`SessionMeta.swarm_mode`,入口:消息体 `swarm_mode`、`/swarm`、headless `--swarm`),AgentLoop 每回合从 SessionManager 读,切换只影响下一回合。两套协作工具互斥:`swarm_mode_hidden_tools` → `ToolCapabilityPolicy::hidden_builtin_tools`(schema 与执行同一谓词),mesh 隐藏 `spawn_subagent` / `wait_subagent` / thread 工具,off / star 隐藏六个 `agent_*`。网状子 agent 的 `parent_session_id` 恒为**根** id(扁平,面板 / 权限冒泡 / purge 全复用星型通道),真实层级是 `SessionMeta.agent_path`(`/root/a/b`),树目录落在根会话目录的 `mesh_agents.json`。服务 `host/session_host/mesh/MeshAgentService` 在 worker / TUI(`SubagentHost`,TUI 主会话是外部根)/ headless 各一份,工具只捕获 `weak_ptr`。几条别改回去的语义:(1) 邮件一律进 AgentLoop 邮箱(`engine/agent/mailbox`),每次模型请求前并入;**子 agent 完成回报不唤醒空闲的父 agent**(Codex 原样),只有 `agent_followup_task` / NEW_TASK 唤醒;interrupted 回合不回报。(2) 信封落盘为 **user 角色** + `metadata.inter_agent`(有意偏离 Codex 的 assistant 角色:连续 assistant 在 Anthropic / DeepSeek 上会被拒),NEW_TASK 算真实用户消息、其余是内部上下文,TUI / Web 都转成系统提示行,别让它变成用户气泡(会开出假回合、触发「重发末尾用户消息」)。(3) 驻留上限 `swarm.mesh.max_concurrent_agents` 默认 4 **含根**,满了按 LRU 换出已结束的子 agent(`registry.destroy`,发消息时 `resume` 恢复),都在跑就报 `agent thread limit reached`。(4) Web 输入框芯片 = 服务端模式 + 未提交的本地选择,**只有二者不同才随消息提交 `swarm_mode`**(`web/src/lib/swarmMode.js`):每条消息都带会把 `/swarm` 刚切的模式改回去,网状树里有 agent 在跑时还会直接 409。回归:`tests/session_host/mesh_agent_service_test.cpp`(真实 SessionRegistry + 脚本化 provider 跑整棵树)、`tests/agent/agent_loop_mesh_mailbox_test.cpp`、`swarmMode.test.js` / `interAgentMessage.test.js`。

`bash_tool` streams cleaned output, polls abort state, truncates very large output, and supports POSIX `stdin_inputs`. File tools should preserve checkpoint hooks by calling `track_file_write_before` before mutating files.

**Prompt cache prefix invariant**:`RequestContextFactory::build()` 每次采样迭代都跑一遍,所以**注入到最后一条真实 user 消息之前的内容必须内容驱动** —— 输入不变就逐字节不变。任何随时间/随请求变化的东西进了那个位置,同一回合内每次工具往返都会把缓存前缀从注入点截断,后面整条尾巴(user 消息 + 全部 assistant/tool 消息)全价重算;工具越多损失越大。曾经的 `[当前环境状态]` 块带秒级时间戳,正好踩中这条。现在 cwd 与日期(只到天,`current_prompt_date`)留在静态 system prompt 的 `# Environment` 里,只在工作目录或日期变化时改变。守护测试:`agent_loop_termination_test.cpp::RequestPrefixIsByteStableAcrossIterationsInATurn`(逐条比对相邻两次请求的公共前缀)+ `system_prompt_test.cpp` 的两个 byte-stable 用例。同理,`cached_context_for_api` 按 cache_key pin 住 session context 内容、git 快照按会话缓存、`ToolExecutor::tools_` 用 `std::map` 保证工具顺序、Anthropic 合成 tool_call_id 走内容哈希 —— 都是为同一条不变量服务的,改动这些地方前先想清楚会不会打穿前缀。
Expand Down
17 changes: 16 additions & 1 deletion docs/daemon-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ Session list endpoints return arrays of objects shaped like:
"title": "Investigate daemon routes",
"title_source": "user",
"summary": "latest user summary",
"swarm_mode": "off",
"created_at": "2026-07-04T01:23:45Z",
"updated_at": "2026-07-04T01:25:00Z",
"provider": "openai",
Expand Down Expand Up @@ -1527,6 +1528,17 @@ with `type:"selection"` are sanitized and expanded into model-visible context
while preserving the user's original display text. Other context objects are
passed as browser context content parts.

`swarm_mode` (optional) switches the session-level swarm mode before the input
is queued: `"star"`, `"mesh"` or `"off"`; legacy clients may send `true` (=
`"star"`) or `false` (= `"off"`). Any other value returns `400` with
`swarm_mode must be "star", "mesh" or false`. The mode is sticky: omitting the
field keeps the session's current mode, and the accepted value is also recorded
as `metadata.swarm_mode` on that user message. Leaving `"mesh"` while agents of
the session's mesh tree are still running (or hold undelivered follow-up tasks)
returns `409` with the reason; so does posting directly to a mesh sub-agent
(`direct input is not allowed for mesh swarm sub-agents`). A successful switch
emits `session_updated {"swarm_mode"}`. See `docs/subagents.md` §9.

`client_message_id` is an optional non-empty string (maximum 256 bytes) used by
Desktop queued-input handoff. When accepted, it is preserved as
`metadata.client_message_id` on the canonical user message so an optimistic
Expand Down Expand Up @@ -4775,7 +4787,10 @@ sends `{"title", "title_source"}`. After each visible user message is persisted
the daemon sends `{"summary"}`: the latest user message's display text
(`metadata.display_text` when present, otherwise `content`) collapsed to one
line and truncated to 80 UTF-8 bytes plus `...`. The same three fields appear in
session listings and in the `GET .../messages?since=0` snapshot. Clients show
session listings and in the `GET .../messages?since=0` snapshot. A swarm-mode
switch sends `{"swarm_mode"}` (`"off" | "star" | "mesh"`); session listings and
the messages snapshot carry the current `swarm_mode` too, plus `agent_path`
(`/root/...`) for mesh sub-agents. Clients show
`title` when it is non-empty (ignoring generated titles that start with
`[Error]`) and `summary` otherwise, in both the session list and the chat
header; they must not derive a title from message bodies.
Expand Down
11 changes: 8 additions & 3 deletions docs/help-source/group5.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@

"swarm": page("把可以独立推进的工作交给子代理,同时保留主任务负责整合、验证和最终交付。", [
section("parallel", "任务分工与并行执行",
'''<p>在输入框左侧<strong>添加能力或上下文</strong>菜单选择<strong>蜂群模式</strong>,输入区出现对应标签。它会让下一条普通消息更积极地派遣子 Agent;开启状态本身不会立即启动任务,也不保证每一个请求都会产生相同数量的子任务。</p><p>适合并行的工作包括分别调查两个独立模块、检查不同平台的入口、准备测试与阅读接口。先说明每个子任务的范围、允许修改的文件和需要返回的结果,避免多个代理同时改同一处内容。</p>''',
'''<p>在输入框左侧<strong>添加能力或上下文</strong>菜单里有<strong>蜂群模式(星型)</strong>与<strong>蜂群模式(网状)</strong>两项,二选一;再点一次已选中的那项即关闭。选中后输入区出现对应标签。</p>''',
table(["模式", "协作方式", "适合的任务"], [
["蜂群模式(星型)", "主任务派出子 Agent,子 Agent 只向主任务汇报最终结果,不再继续派生。", "几项互不相关、各自汇报即可的调查或检查。"],
["蜂群模式(网状)", "任意 Agent 都能继续派生下级(路径形如 /root/research/web),可以互相发消息、追加后续任务,并等待下级回报。", "需要分层拆解、中途协调或多轮往返的大任务。"],
]),
'''<p>蜂群模式是会话级设置:随下一条消息生效并一直保持,直到切换或关闭;TUI 与对话中都可以用 <code>/swarm star</code>、<code>/swarm mesh</code>、<code>/swarm off</code> 切换,<code>/swarm</code> 查看当前模式。开启状态本身不会立即启动任务,也不保证每一个请求都会产生相同数量的子任务。</p><p>适合并行的工作包括分别调查两个独立模块、检查不同平台的入口、准备测试与阅读接口。先说明每个子任务的范围、允许修改的文件和需要返回的结果,避免多个代理同时改同一处内容。</p>''',
code("并行检查前端表单与后端校验:一位代理只阅读前端并列出边界情况,另一位只阅读后端并找出校验差异。由主任务汇总证据后提出修复方案,暂不修改文件。", "可独立并行的任务示例"),
'''<p>子代理拥有独立上下文,并继承父任务的工作目录与权限模式。主任务通常收到子代理最终答复,而非自动把所有中间工具输出复制进自身上下文。共享工作目录意味着文件变化会相互可见;独立上下文不等于独立 Git 工作树。</p>''',
'''<p>子代理拥有独立上下文,并继承父任务的工作目录与权限模式。主任务通常收到子代理最终答复,而非自动把所有中间工具输出复制进自身上下文。共享工作目录意味着文件变化会相互可见;独立上下文不等于独立 Git 工作树。</p><p>网状模式下,子 Agent 默认继承派生者的对话上下文(只保留用户消息与最终回答),也可以只继承最近几轮或完全不继承。下级完成或出错时,结果以「某某已完成」「某某执行出错」提示行送到上级,不会打断上级正在进行的工作,上级下一次回复时会看到它。同一棵协作树同时最多保留 4 个 Agent(含主任务,可在配置文件的 <code>swarm.mesh.max_concurrent_agents</code> 调整);满了会暂时卸载最久没有活动、已经结束的 Agent,再次给它发消息时自动恢复。仍有子 Agent 在运行时不能退出网状模式,先等待它们完成或停止它们。</p>''',
figure("AD-03", "开启蜂群模式并提交分工", "展示添加能力菜单中的蜂群模式、开启后的标签,以及说明任务边界的输入内容。")),
section("progress", "查看进度与处理操作确认",
'''<p>产生子任务后,在主会话的<strong>后台任务</strong>面板查看运行中和已完成的任务,打开任务可阅读其会话与工具结果。子任务归属于父会话,不会作为普通任务充满主侧边栏和全局搜索结果。</p><p>来自子任务的权限请求与问题会在主界面出现,并标注来源。检查任务名称、工具参数与目标路径后处理;不要仅因为主任务尚有输出,就忽略另一个子任务正在等待回答。</p><p>可以在面板中停止运行中的子任务。停止父任务也会向其子任务传播中断;已经产生的文件和记录会保留,不自动回滚。清除已结束任务会删除这些子会话记录,确认不再需要查看后再清理。</p>''',
code("/tasks\n/tasks list\n/tasks abort 子任务ID\n/tasks clear", "TUI · 管理后台任务"),
figure("AD-04", "后台任务与来自子任务的请求", "展示运行中与已完成分组、查看会话和停止入口,以及带来源名称的权限确认。"),
'''<p>全部子任务完成后,让主任务检查它们之间的冲突、统一差异并运行必要验证。各自报告“完成”不能代替整合后的构建或测试。</p>''')
], ["web/src/components/InputBar.jsx", "web/src/components/ComposerSessionControls.jsx", "web/src/components/SubagentPanel.jsx", "src/apps/tui/commands/builtin_commands.cpp", "docs/subagents.md"]),
], ["web/src/components/InputBar.jsx", "web/src/components/ComposerSessionControls.jsx", "web/src/components/SubagentPanel.jsx", "src/apps/tui/commands/builtin_commands.cpp", "src/apps/tui/commands/swarm_mode_command.cpp", "src/host/session_host/mesh/mesh_agent_service.cpp", "docs/subagents.md"]),

"schedules": page("把清晰且可重复的任务保存为循环,按周期、间隔或指定时间执行,并通过记录检查每次结果。", [
section("create", "创建与设置执行时间",
Expand Down
2 changes: 2 additions & 0 deletions docs/help-source/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@
"web/src/components/ComposerSessionControls.jsx",
"web/src/components/SubagentPanel.jsx",
"src/apps/tui/commands/builtin_commands.cpp",
"src/apps/tui/commands/swarm_mode_command.cpp",
"src/host/session_host/mesh/mesh_agent_service.cpp",
"docs/subagents.md"
]
},
Expand Down
Loading
Loading