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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,11 @@ Core tools are registered for both TUI and daemon paths: `bash`, `file_read`, `f

`spawn_subagent` / `wait_subagent` are registered in both daemon (`worker.cpp`) and TUI (via `src/apps/tui/subagent_host.{hpp,cpp}` — SessionRegistry/LocalSessionClient have no web dependency, so the TUI process instantiates them directly; the TUI main session lives outside the registry, so its permission mode reaches children through `SubagentToolDeps::fallback_permissions`, and `on_spawn` lets the host register tasks + subscribe child events): a sub-agent is a normal SessionRegistry session (isolated context) created with the parent's cwd and permission mode. `spawn_subagent(prompt, wait=true)` blocks until the child turn finishes and returns its final assistant reply into the parent context; `wait=false` is fire-and-forget for pipeline handoff, joined later via `wait_subagent(session_id)`. Prompts starting with `/` go through the same skill-command expansion as Web input. Sub-agents cannot spawn further sub-agents (`SessionEntry::subagent_depth`). Implementation: [src/host/session_host/tools/spawn_subagent_tool.cpp](src/host/session_host/tools/spawn_subagent_tool.cpp); deps are late-bound via shared_ptr because ToolExecutor is constructed before SessionRegistry in worker.cpp.

**TUI surface**: the right sidebar's "Background Tasks" section lists running sub-agents only (● title + elapsed; removed the moment the child turn ends — user decision); `/tasks [list|abort <id>|clear]` is the operation entry (clear = same permanent-purge semantics as Web, via `SessionStorage::purge_session_files`). A child's `permission_request` is queued (`TuiState::remote_confirm_queue`) and pumped into the confirm overlay when free (origin-labelled; the answer routes back via `SubagentHost::respond_permission`). AskUserQuestion needs no bridging — children share the TUI ToolExecutor, so the TUI ask tool runs directly; it now queues on `TuiState::overlay_cv` until the confirm/ask overlay is free and sets `ask_origin_label` when the caller is a sub-agent. `on_tool_confirm` in tui/app/tui_agent_bridge.cpp queues on the same cv, so concurrent overlay claims (main confirm / child ask / remote pump) serialize instead of clobbering each other.
**TUI surface**: the right sidebar's "Background Tasks" section lists running sub-agents only (● title + elapsed; removed the moment the child turn ends — user decision); `/tasks [list|abort <id>|clear]` is the operation entry (clear = archive, same as the Web panel: unload + `meta.archived`, records kept). TUI exit cleanup (`cleanup_old_sessions`) counts only main sessions against `max_sessions` and deletes children together with their main session; orphans whose parent is gone count as main sessions. A child's `permission_request` is queued (`TuiState::remote_confirm_queue`) and pumped into the confirm overlay when free (origin-labelled; the answer routes back via `SubagentHost::respond_permission`). AskUserQuestion needs no bridging — children share the TUI ToolExecutor, so the TUI ask tool runs directly; it now queues on `TuiState::overlay_cv` until the confirm/ask overlay is free and sets `ask_origin_label` when the caller is a sub-agent. `on_tool_confirm` in tui/app/tui_agent_bridge.cpp queues on the same cv, so concurrent overlay claims (main confirm / child ask / remote pump) serialize instead of clobbering each other.

Full subagent reference (design, wire protocol, Web panel, TUI bridging, test map): [docs/subagents.md](docs/subagents.md).

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.
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. **Children are kept like ordinary sessions (user decision):** the panel's「归档」archives settled children (`PUT /api/workspaces/:hash/sessions/:id/archive`; hidden from `?parent=` and from the `?archived=1` list, records kept), and permanently deleting an archived main session (`purge_session_data`) cascades to all its children — any running child → 409, children go first so a failure keeps the parent retryable. A mesh agent archived from the panel is unarchived when addressed again (`MeshAgentService::ensure_loaded`). The panel's read-only transcript passes the parent's `workspaceHash`: the Desktop daemon serves many workspaces and an unloaded child (after a Desktop restart or a mesh eviction) is otherwise 404 SESSION_NOT_FOUND. 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`。

Expand Down
19 changes: 16 additions & 3 deletions docs/daemon-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -812,13 +812,22 @@ the user-message search index, then removes `<id>.jsonl`, the per-session
`<id>/` persisted-data directory, and `<id>.meta.json` last. Returns `204` only
after cleanup succeeds.

Sub-agent sessions (`spawn_subagent` tasks and every mesh agent of the tree)
are kept like ordinary sessions and live and die with their main session:
purging a main session first applies the same cleanup to each of its child
sessions (archived or not, including children that exist only in memory), then
to the main session itself. A failure stops before the main session is removed,
so the operation stays retryable.

Guard rails and errors:

- `400` when `purge=1` is missing or the session id is invalid
- `404` when the workspace or session does not exist
- `409 {"error":"session must be archived before permanent deletion"}` when
the target is not archived
- `409` when the target is unexpectedly busy
- `409 {"error":"subagent session <id> is busy; abort it first"}` when one of
the main session's child sessions is running a turn; nothing is deleted
- `500` when search-index or file cleanup fails; metadata is retained until
the other known session data has been removed so the operation remains
retryable
Expand Down Expand Up @@ -1079,9 +1088,13 @@ thread, and removes it from the registry. It does not delete disk history.
Returns `204`; returns `503` when the session client is unavailable.

`DELETE /api/sessions/:id?purge=1` performs the same durable cleanup for either
an archived main session or a sub-agent session. It remains the background-task
"clear" action for sub-agents and is also the compatibility fallback used by
the archived-session settings page. Guard rails:
an archived main session (cascading to its child sessions as above) or a single
sub-agent session. It is the compatibility fallback used by the
archived-session settings page. The background-task panel no longer purges:
its "archive" action uses `PUT /api/workspaces/:hash/sessions/:id/archive`
(or `PUT /api/sessions/:id/archive` without a workspace), which unloads the
child, hides it from `?parent=` listings and keeps its records. Archived child
sessions never appear in the `?archived=1` list. Guard rails:

- `400 {"error":"only subagent sessions can be purged"}` for a non-archived
main session
Expand Down
2 changes: 1 addition & 1 deletion docs/help-source/group5.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
'''<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>''',
'''<p>产生子任务后,在主会话的<strong>后台任务</strong>面板查看运行中和已完成的任务,打开任务可阅读其会话与工具结果。子任务归属于父会话,不会作为普通任务充满主侧边栏和全局搜索结果。</p><p>来自子任务的权限请求与问题会在主界面出现,并标注来源。检查任务名称、工具参数与目标路径后处理;不要仅因为主任务尚有输出,就忽略另一个子任务正在等待回答。</p><p>可以在面板中停止运行中的子任务。停止父任务也会向其子任务传播中断;已经产生的文件和记录会保留,不自动回滚。</p><p>子任务的会话记录与普通会话一样长期保留。面板中的<strong>归档</strong>和 TUI 的 <code>/tasks clear</code> 只把已结束任务从列表中收起,不删除记录;网状模式下被收起的 Agent 再次收到消息时会自动恢复并重新出现在面板中。只有把主会话归档后再永久删除,它的全部子任务记录才会随之删除。</p>''',
code("/tasks\n/tasks list\n/tasks abort 子任务ID\n/tasks clear", "TUI · 管理后台任务"),
figure("AD-04", "后台任务与来自子任务的请求", "展示运行中与已完成分组、查看会话和停止入口,以及带来源名称的权限确认。"),
'''<p>全部子任务完成后,让主任务检查它们之间的冲突、统一差异并运行必要验证。各自报告“完成”不能代替整合后的构建或测试。</p>''')
Expand Down
Loading
Loading