9.5 KiB
9.5 KiB
Sub-Agent Activity WebUI Design
Goal
Restructure the WebUI around sub-agent activity while moving sub-agent definition management into the settings page. Concretely:
- Move sub-agent definition management (CRUD, enable/disable, role prompt) out of the
agentsnavigation item into a new "子代理" tab on the settings page, and enlarge the role-prompt editing area. - Turn the
agentsnavigation item into an activity monitor that shows running sub-agents and historical run results. - Add a read-only, chat-like detail view for a single sub-agent run, including a full, incrementally streamed message transcript.
- Reduce the
taskspage to scheduled-task information only; move the background-task listing into the sub-agent activity page.
Non-goals
- No changes to sub-agent orchestration semantics (delegation, budgets, signals, run admission).
- No WebUI routing framework; the detail view is an in-page sub-view.
- No persistence of the main agent's chat history changes; the transcript work is scoped to sub-agent runs.
Backend
Schema (v9)
Add a new agent_run_messages table for incrementally streamed sub-agent transcripts:
CREATE TABLE IF NOT EXISTS agent_run_messages (
id TEXT PRIMARY KEY,
run_id TEXT NOT NULL,
seq INTEGER NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
reasoning_content TEXT,
tool_call_id TEXT,
tool_name TEXT,
tool_calls_json TEXT,
created_at INTEGER NOT NULL,
FOREIGN KEY (run_id) REFERENCES agent_runs(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_agent_run_messages_run_seq ON agent_run_messages(run_id, seq);
- Bump
SCHEMA_VERSIONfrom8to9insrc/storage/mod.rs. - Add the table DDL to
src/storage/agent_run.rs::AGENT_SCHEMA_STATEMENTS. - Version-gate the existing agent-table drops.
migrate_schemacurrently dropsagent_runs/agent_inbox_events/agent_run_groupsunconditionally whenevercurrent < SCHEMA_VERSION; on a v8→v9 upgrade this would destroy run history. Gate those drops (and thebackground_tasksdrop) oncurrent < 8— the batch-group removal is a pre-v8 concern — so a v8→v9 upgrade only adds the new table and preserves existing runs. When the drops do run (pre-v8), dropagent_run_messagesbeforeagent_runsso foreign-key enforcement never blocks the implicit row delete. - Update the v8 schema test that hardcodes
assert_eq!(version, 8)(fresh_database_creates_schema_v8_agent_tables) to v9 and rename it accordingly.
We deliberately do not add a transcript column to agent_runs. Incremental append rows in agent_run_messages are the single source of truth for transcripts.
Incremental capture
- Add an optional sink to
AgentLoop:- field
transcript_sink: Option<tokio::sync::mpsc::UnboundedSender<ChatMessage>> - builder
with_transcript_sink(sender) -> Self - at every site where a message is appended to
emitted_messages(assistant messages and tool result messages), forward a clone to the sink. Convertappend_steering_messagesfrom an associated function to a method so it can forward too (sub-agents never use steering, but the sink must be consistent for future use).
- field
src/agent/sub_agent.rs::execute_resolved:- create an
mpsc::unbounded_channel - pass the sender via
build_sub_agent_with_provider→with_transcript_sink - spawn a writer task that owns the receiver and a
seqcounter; for each message it stripsprovider_state(set toNone) and doesINSERT INTO agent_run_messages - restructure so the sender is dropped and the writer task is awaited in every exit path, including the
tokio::select!cancellation arm that currently returns early withoutprocess_with_contextreturning; only after the writer drains does the terminal commit run - the writer uses
self.storage(the manager already holdsOption<Arc<Storage>>); if storage is absent, the writer becomes a no-op
- create an
The task prompt is not duplicated into the table; the detail view renders run.task as the leading user bubble.
Storage API
Add to src/storage/agent_run.rs:
append_agent_run_message(run_id, seq, &message, now) -> Result<()>— single-row insert.list_agent_run_messages(run_id, limit) -> Result<Vec<AgentRunMessageRecord>>— ordered byseq. The transcript is naturally bounded: one run emits at most a handful of messages per tool iteration and iterations are capped by the definition'slimits.max_iterations(default 99); defaultlimitof10_000is a generous ceiling, not a pagination contract.get_agent_runuses this same default limit when loading the transcript.
Two distinct types to avoid a storage/protocol collision:
AgentRunMessageRecord(storage, insrc/storage/agent_run.rs): carries the raw columns —id, run_id, seq, role, content, reasoning_content, tool_call_id, tool_name, tool_calls_json, created_at.AgentTranscriptMessage(protocol, insrc/protocol.rs): the serialized shape — same fields excepttool_calls_jsonis parsed intoVec<providers::ToolCall>; aFrom<AgentRunMessageRecord>impl performs the parse.
HTTP API
GET /api/agent-runs/{id}currently returns{ "run": AgentRunView }. Extend the response to{ "run": ..., "session_id": ..., "transcript": [ ... ] }where:session_idisrun.root_session_id(theAgentRunViewdeliberately omits it, so it is added at this endpoint's response level)transcriptis the ordered list ofAgentTranscriptMessagerows (exposingreasoning_contentbut neverprovider_state)
GET /api/agent-runs/{id}/eventsis unchanged (signals/completions).GET /api/tasksis unchanged and already lists all runs in any status; the activity page consumes it.
Frontend
Settings page (SettingsPage.svelte)
- Add a "子代理定义" tab to the existing vertical
Tabs(named to disambiguate from the "子代理" activity nav item). Move the definition list and the editor modal fromAgentsPage.sveltehere verbatim, then:- enlarge the role-prompt
textarea(min-height~360px, full-width, monospace) - widen the editor modal (
min(920px, 94vw)) and put the role-prompt field on its own row - keep the existing API calls (
/api/agents,/api/agents/options, POST/DELETE) unchanged
- enlarge the role-prompt
Sub-agent activity page (AgentsPage.svelte, rewritten)
- List view:
- "活动中" section: runs in
queued/running/waiting_childrenstatus, with a pulse indicator,agent_id, prompt excerpt,mode/depth, and elapsed time. - "历史活动" section: terminal runs (
completed/failed/timed_out/cancelled/interrupted), newest first, withStatusBadge,agent_id, prompt excerpt,tool_calls_count/iterations, timestamps, and a "详情" action. - Poll
GET /api/tasks?limit=200every 5s while mounted.
- "活动中" section: runs in
- Detail view (in-page sub-view, back button, read-only):
- metadata header:
agent_id,StatusBadge,provider/model,mode,depth,tool_calls_count,iterations,session_id,started_at/finished_at, duration,parent_run_idif present - leading "task" bubble from
run.task - transcript messages rendered like the chat page:
Markdownfor content, collapsible reasoning block,ToolCallCardfor tool calls. Pair each assistant message'stool_callswith itstool-role result bytool_call_id(mirroringChatPage'stoolResult()), so calls and results remain independently collapsible. - signal cards from
GET /api/agent-runs/{id}/events(same rendering as the chat page's agent-event cards) - final
errorcard on failure - while
statusis non-terminal, pollGET /api/agent-runs/{id}+/eventsevery 2s to stream the growing transcript; stop polling on terminal status
- metadata header:
- No composer, no input, no interactive actions other than navigation/collapse.
Tasks page (TasksPage.svelte)
- Remove the "后台任务" tab and the
Tabswrapper; keep only the scheduled-job list (jobs + runs dots), which already comes from/api/jobsand/api/jobs/{id}/runs.
Navigation (App.svelte)
- Update the
agentspage description to reflect activity monitoring ("查看活动中的子代理与历史运行"); keep the nav label "子代理". - Update the
taskspage description to scheduled tasks only ("管理定时任务").
Error handling
- Missing/empty transcript: detail view renders the task bubble + metadata + error (or a "无转录" placeholder for failed runs).
- Storage write failures in the transcript writer are logged and the run still commits its terminal status; a broken transcript never fails the run.
- API auth/5xx reuse the existing
api()error handling andnotifytoast path.
Testing
- Rust: v9 migration from a v8 database preserves existing
agent_runsrows (version-gated drops), and the pre-v8 legacy rebuild path still works;append_agent_run_message+list_agent_run_messagesround-trip withseqordering;provider_stateis stripped from persisted transcript rows; sink drains on success, timeout, and cancellation before terminal commit (including the early-return cancellation arm);get_agent_runreturns the transcript andsession_id;AgentRunViewlist responses remain transcript-free; the v8 hardcoded schema-version assertion is updated to v9. - Run
cargo test --libandcargo clippy --all-targets --all-features -- -D warnings. - Frontend:
cd webui && npm run check && npm run build, thencargo buildto verify theOUT_DIRembedding path. No external runtime dependency; the browser stays on/wsand the existing HTTP endpoints.