Skip to content

feat(tui): async delegation view — docked agents panel + live steering - #70899

Open
JoaoMarcos44 wants to merge 12 commits into
NousResearch:mainfrom
JoaoMarcos44:feat/async-delegation-view
Open

feat(tui): async delegation view — docked agents panel + live steering#70899
JoaoMarcos44 wants to merge 12 commits into
NousResearch:mainfrom
JoaoMarcos44:feat/async-delegation-view

Conversation

@JoaoMarcos44

@JoaoMarcos44 JoaoMarcos44 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an inline, docked agents panel above the composer plus a live steering channel so a running subagent can be redirected by id (@b7c2 switch approach) without killing it. Watch background delegations next to the conversation; steer one mid-flight.

Closes #70894.

For maintainers: this PR is opened from the fork branch JoaoMarcos44:feat/async-delegation-view with "Allow edits by maintainers" enabled — you can push directly to the branch, adjust, or merge. To check it out locally:

gh pr checkout <this-PR-number>   # or:
git fetch https://github.com/JoaoMarcos44/hermes-agent feat/async-delegation-view && git checkout FETCH_HEAD

Motivation

Background delegation and the subagent tree were disconnected: the parked turn was a one-line hint, and the rich tree was a full-screen /agents modal that hid the conversation. There was no way to watch background agents inline, and the only lever on a wrong-headed background fixer was x (kill). This adds visibility and a steering lever.

~80% of the work is reprojection of data that already exists. The one genuinely new capability — inbound steering — reuses the child's existing AIAgent.steer() drain and iteration-boundary interrupt check, so no new core-loop code is introduced.

What changed

Phase 1 — docked read-only panel (low risk)

  • delegation.async_list RPC: pure read projection of the async-delegation registry (interrupt_fn already stripped server-side).
  • $asyncDelegations store + applyAsyncList; the existing 5s gateway tick populates it alongside delegation.status.
  • agentsPanel.tsx: merges live in-turn subagents (turnStore.subagents, freshest tool + elapsed) with background/async rows into one list. Renders nothing when there are no agents.
  • Extracted STATUS_GLYPH into a shared module so the panel and the /agents overlay never diverge visually.
  • Mounted above the composer next to LiveTodoPanel; header click collapses; ^a opens the full tree.

Phase 2 — live steering (@id)

  • send_to_subagent mirrors interrupt_subagent: looks the child up in the live registry and calls AIAgent.steer(text). The child drains it at its next iteration boundary and appends it as a delimited out-of-band user turn on the last tool message — never spliced mid-tool — so role alternation and the prompt cache stay intact.
  • subagent.send RPC.
  • Composer routes @<id> text to steering only when the token resolves to a live subagent (resolveSteerTargetId), so an ordinary prompt like email @john later falls through to a normal turn untouched. Feedback toast: delivered → @b7c2 / @b7c2 already finished.

Design decisions (deliberate scope calls)

  • No per-session ownership guard on send_to_subagent — the live registry carries no session key and interrupt_subagent is already unguarded (holding the id is authority). Same trust model; a guard would require a new registry field. Noted as follow-up.
  • Panel actions = read-only + open-tree. Full keyboard kill/inject would hijack the global input router (high risk). Kill stays reachable via the existing /agents overlay; the docked panel delivers the core value — visibility — without that risk.

Testing

155 tests across 7 suites, all green (127 frontend + 28 backend).

Suite Covers Tests
agentRows.test.ts merge logic · running/done counts · elapsed clocking · status aliases · PANEL_MAX_ROWS bound · DONE_LINGER_MS retirement · dropCoveredBatches 35
asyncDelegationE2E.test.tsx full loop: RPC payload → store → painted frame · steer round-trip · goal truncation · id discoverability 34
tests/test_async_delegation_view.py RPC shapes · send_to_subagent · 50-thread concurrency · role-legality · status transitions · server-side projection 28
subagentSteer.test.ts @id parsing · id/prefix resolution · ambiguity refusal · normal-text passthrough 24
agentsPanel.test.tsx header counts · empty state · collapse · result ready ⏎ · /agents label · tree-click isolation 16
submissionCore.test.ts steer dispatch · transcript echo · delivered vs failed paths 13
delegationStoreAsync.test.ts applyAsyncList populate · null tolerance · replace-not-append · snapshot guard 5

Panel tests render through Ink with renderSync and assert on the painted frame, not on the returned element tree — so the height bound, truncation and dedupe are checked as the terminal actually shows them.

Regression: the pre-existing failures (terminal*/editor/memory TS suites and the tests/tools/test_async_delegation.py cases) were re-confirmed identical on a baseline worktree with this branch absent — a Python 3.14 _initializer stdlib incompatibility plus unrelated suites, with zero references to the new code in any traceback. None of the failing files are touched by this PR. Build ✓ · lint ✓ · typecheck ✓.

Provider evidence — steering does not break the prompt cache

The design claim that a steer injected at the tool-iteration boundary preserves the cached prefix was measured against the live provider (Anthropic direct, claude-haiku-4-5-20251001, OAuth), not just asserted in mocks:

Call input cache write cache read latency cost
cache_warm 11 6896 0 1992 ms $0.008656
cache_read 11 0 6896 944 ms $0.0007306
cache_safe_steering 89 0 6896 1097 ms $0.0008186

The steering call reads back the same 6896-token prefix as the plain read — injecting the steer message costs 78 extra input tokens and zero cache invalidation. Total spend: $0.0102052 against a $0.05 cap.

Negative control: an orphan tool_result (role alternation deliberately broken) was rejected with HTTP 400 BadRequestError. That is precisely the failure the queue-drain-at-iteration-boundary design avoids, so the guard is load-bearing rather than defensive decoration.

Files (26 changed · +3114 / −29 · 25 code + 1 infographic)

Backend: tools/async_delegation.py · tools/delegate_tool.py · tui_gateway/server.py
Frontend: components/agentsPanel.tsx · components/agentsOverlay.tsx · components/appLayout.tsx · lib/agentRows.ts · lib/subagentGlyph.ts · lib/subagentSteer.ts · app/delegationStore.ts · app/turnStore.ts · app/submissionCore.ts · app/useSubmission.ts · app/useMainApp.ts · app/createGatewayEventHandler.ts · hooks/useCompletion.ts · content/hotkeys.ts · gatewayTypes.ts
Tests: the 7 suites above.


🖼️ Architecture at a glance

Async Delegation View — technical architecture: TUI composer → resolveSteerTargetId → subagent.send / delegation.async_list RPCs → AIAgent.steer() queue drained at the tool iteration boundary, preserving role alternation and prompt cache, feeding the LiveAgentsPanel status glyphs

JoaoMarcos44 and others added 4 commits July 22, 2026 22:38
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
Phase 1 (read-only docked panel):
- delegation.async_list RPC projects the async registry to the TUI
- $asyncDelegations store + applyAsyncList; 5s gateway tick populates it
- agentsPanel.tsx merges live in-turn subagents with background async rows
- extract shared STATUS_GLYPH so panel and /agents overlay never diverge
- mount above the composer next to LiveTodoPanel; ^a opens the full tree

Phase 2 (live steering, @id):
- send_to_subagent reuses the child's existing AIAgent.steer() drain, so the
  steer lands as a fresh out-of-band user turn on the last tool message at the
  iteration boundary — role alternation and prompt cache stay intact. No new
  core-loop code.
- subagent.send RPC; composer routes "@<id> text" only when the token resolves
  to a live subagent, so ordinary prompts fall through untouched.

Tests: 53 new (backend 15 incl. 50-thread concurrency + role-legality;
frontend 38 across merge logic, steer resolution, panel view, store).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@JoaoMarcos44

Copy link
Copy Markdown
Contributor Author

Follow-up pushed in ec66bc9 to close the gaps found during verification:

  • @delegation_id now steers running �ackground=true delegation units through a transient steer_fn; batch steering reaches every still-live child.
  • async control closures are stripped from RPC snapshots and released on completion.
  • LiveAgentsPanel now sits outside the transcript ScrollBox, fixed above the prompt/composer region.
  • async delegation snapshots refresh through the existing 1.5s TUI session poll.
  • exact/prefix resolution remains fail-closed for ambiguous or finished targets.

Validation: 17 backend tests + 39 focused TUI tests passed;
pm run typecheck and
pm run lint passed.

Video was not attached: current Windows environment has no terminal capture backend (ffmpeg/asciinema unavailable). Existing local Ink demo was launched for smoke validation, but remains untracked and was not added to the PR.

@JoaoMarcos44

JoaoMarcos44 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Expanded demo with provider-verified evidence: the video now covers the attached panel, live steering, cache-safe flow, Anthropic metrics (6,896 tokens generated; 6,896 read in subsequent calls), cost/latency, and HTTP 400 negative control. Estimated total cost: US$0.0102052. Direct OpenAI usage is explicitly marked as untested due to the absence of credentials. Commit: 27e04e7d3. Artifacts: async-delegation-demo.mp4, async-delegation-provider-evidence.json, and relatorio-cobertura-video-async-delegation.md.

@JoaoMarcos44
JoaoMarcos44 force-pushed the feat/async-delegation-view branch from 27e04e7 to bc9cb2e Compare July 24, 2026 19:30
@JoaoMarcos44

Copy link
Copy Markdown
Contributor Author

Removed async-delegation-demo.mp4 from the PR and rewritten branch history with force-with-lease, as requested. Added relatorio-controle-negativo-async-delegation.md explaining the intentional Anthropic HTTP 400 negative control, scope, privacy, and cost. New head: bc9cb2e.

@OutThisLife OutThisLife left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by checking the branch out and rendering AgentsPanelView through real Ink (renderSync) across a few realistic scenes, plus tracing the RPC/store wiring against main. npx tsc --noEmit and eslint on the touched files are clean, and all 39 new frontend tests pass locally — the backend half of this is the strong half. The docked panel itself isn't ready yet.

The backend approach is right and I want to say so first: reusing the child's existing AIAgent.steer() slot and the apply_pending_steer_to_tool_results drain means steering lands on the last tool result at an iteration boundary with no new core-loop code, so role alternation and the cached prefix survive. Extracting STATUS_GLYPH into lib/subagentGlyph.ts so the panel and the /agents overlay can't drift is exactly the right call, and keeping agentRows.ts / subagentSteer.ts pure and ink-free makes them properly testable.

Blocking — the panel

Here is the actual rendered output at 72 columns:

── typical: one live, one background running, one done ─────────────────
▾ agents · 2 running · 1 done  ^a tree
   1 ● map auth handshake edge cases  42s  read_file
   2 ● fixer patch token-bucket refill race  2m 10s  running
   3 ✓ tests sweep flaky gateway suite  2m 5s  result ready ⏎

That case looks good. The problems show up in the cases that will actually occur.

1. Finished rows never leave, and the panel is fixed chrome. list_async_delegations() returns running and completed records, retained up to _MAX_RETAINED_COMPLETED = 50. The panel renders every one of them, and unlike LiveTodoPanel (which rides inside the transcript ScrollBox) this one is mounted between the transcript and PromptZone, so it permanently eats terminal rows:

── accumulated history: 12 finished background delegations ─────────────
▾ agents · 0 running · 12 done  ^a tree
   1 ✓ fixer background task 1  1m 10s  result ready ⏎
   2 ✓ fixer background task 2  1m 11s  result ready ⏎
   … 10 more …

Twelve background delegations into a session, twelve permanent rows sit above your composer saying "result ready" for results that re-entered the conversation minutes ago. Worst case is ~50. Finished rows need to age out (or the panel needs to cap and show "+N more"), and the suffix promises a keypress that does nothing.

2. Goals aren't truncated. row.goal renders into a bare <Text> with no wrap="truncate-end", so one background delegation with a realistic goal is five lines of fixed chrome:

── long goal (real delegate_task goals are paragraphs) ─────────────────
▾ agents · 1 running · 0 done  ^a tree
   1 ● fixer Investigate the intermittent 429 responses coming from the
  OpenRouter provider adapter during high-concurrency batch runs,
  reproduce with a synthetic load harness, and propose a retry/backoff
  change that does not break the existing credential-pool rotation
  logic.  2m 10s  running

The continuation lines also lose the row indent. This is worse than it looks for batches: dispatch_async_delegation_batch builds combined_goal as "3 parallel subagents: <40 chars>; <40 chars>; <40 chars>", so every fan-out produces one of these walls. The neighbouring surface already solves this — ListRow in agentsOverlay.tsx uses compactPreview(goal, width - 28 - depth * 2) plus wrap="truncate-end". Match it.

3. finalizing renders as a red error glyph on the success path. _finalize() sets record["status"] = "finalizing" while durable persistence and queue publication run, then flips to the terminal status. finalizing isn't in STATUS_GLYPH, so statusGlyph() falls through to the error entry, and the header counts it in neither bucket:

── transient finalizing status ─────────────────────────────────────────
▾ agents · 0 running · 0 done  ^a tree
   1 ⚠ fixer patch token-bucket refill race  2m 10s  finalizing

Every successful background delegation flashes a red ⚠ next to a header claiming zero agents. The defensive ?? STATUS_GLYPH.error fallback is fine for genuinely unknown cross-version statuses, but finalizing is a status this code path produces on purpose and should map to something non-alarming.

4. ^a tree advertises a keybinding that doesn't exist. There is no ctrl-A handler anywhere in the TUI — isCtrl(key, ch, …) is only wired for c and x in useInputHandlers.ts, and patchOverlayState({ agents: true }) is reached solely from /agents in app/slash/commands/ops.ts. Either add the binding or label the affordance with the command that actually works.

5. Clicking "^a tree" also collapses the panel. The ^a tree <Text onClick> is a child of the header <Box onClick={onToggle}>, and dispatchClick in hermes-ink/src/ink/hit-test.ts walks up through parents until a handler calls stopImmediatePropagation(). So one click opens the overlay and toggles collapse. The convention for nested clickables is already established in appChrome.tsx:573, activeSessionSwitcher.tsx:538, and appLayout.tsx:301 — call event.stopImmediatePropagation?.() in onOpenTree.

6. The panel never shows an agent id, so you can't steer from it. @<id> text is the headline feature, and the row renders index, glyph, role, goal, elapsed, and detail — no id. The /agents list rows don't show ids either; only the detail pane does. So the loop is: open /agents, arrow to the agent, read the id out of the detail pane, close the overlay, type @b7c2 …. The panel should print the short id (it's already the row key), and the header is the natural place for a @id to steer hint.

7. Live and async rows aren't deduped. buildAgentRows keys live rows live:<subagent_id> and async rows async:<delegation_id>, and a background fan-out is one async record covering N children that also arrive individually as live subagents via subagent.start. A background batch of three renders as three live rows plus a fourth row carrying the concatenated batch label — four rows for three agents.

Blocking — polling and payload

8. applyAsyncList re-renders the TUI on every tick, unconditionally. It does $asyncDelegations.set(...) with a fresh array every poll whether or not anything changed, so LiveAgentsPanel (subscribed via useStore) re-renders every 1.5s forever, including on a completely idle session with zero delegations. There's a comment about precisely this ~15 lines above the new call site in useMainApp.ts:

Only patch when something actually changed. patchUiState always produces a new state object, which notifies every $uiState subscriber; patching unconditionally on each 1.5s poll re-renders the whole TUI and causes idle flicker.

Same hazard, same file. Bail out when the snapshot is unchanged. Related: delegation.async_list is now polled from two places — useMainApp at 1.5s and the existing tick in createGatewayEventHandler — so pick one.

9. The RPC ships the whole record. list_async_delegations() strips only the two closures, so every 1.5s poll carries context (arbitrary agent-supplied text, can be large), goals, toolsets, session_key, origin_session_id, and parent_session_id for up to 50 records, to render six fields. Project server-side to what the panel actually uses.

Non-blocking

  • Please drop the evidence artifacts from the diff: relatorio-cobertura-video-async-delegation.md, relatorio-controle-negativo-async-delegation.md, async-delegation-provider-evidence.json, and scripts/async_delegation_provider_evidence.py. The last one is a one-off harness that spends real money against whatever Anthropic OAuth credential is resolved and writes a JSON file to the repo root; it isn't something we want to carry in scripts/. Put the numbers in the PR body instead. The infographic is fine.
  • appLayout.tsx line 238 is a no-op reformat of the LiveTodoPanel line — revert it to keep the diff honest.
  • steer_fn on the singular dispatch_async_delegation has no production caller; all background delegation goes through dispatch_async_delegation_batch. Harmless symmetry with interrupt_fn, just noting it's untriggered.
  • @<id> has no /help entry, no completion, and no docs, so the only way to discover it is this PR description.
  • The steer text itself never appears in the transcript — you get delivered → @b7c2 but no record of what you sent.
  • agentsPanel.test.tsx calls AgentsPanelView({...}) as a plain function and walks the returned element tree, which is why none of items 1-5 were caught. Rendering through Ink and asserting on the frame would have surfaced the wrapping, the finalizing glyph, and the row accumulation.

Happy to re-review once the panel bounds its own height, truncates, stops advertising ^a, and shows the ids that make @<id> usable. The steering backend I'd take close to as-is.

Numbers move to the PR body. The provider evidence harness spent real
money against a resolved OAuth credential and wrote to the repo root;
it is not something to carry in scripts/. Infographic is kept.
Blocking items from review:

1. Panel had no height bound and completed rows never left. Cap at
   PANEL_MAX_ROWS=5 and retire finished rows after DONE_LINGER_MS=60s.
2. Goals rendered untruncated into fixed chrome. Row is now a single
   line with wrap="truncate".
3. "finalizing" fell through statusGlyph() to the error entry on the
   success path. Added its own entry and counted it as in-flight.
4. Header advertised "^a tree", which has no handler. Label is now
   "/agents", the command that actually works.
5. The tree affordance was nested inside the header's onClick, so one
   click both opened the overlay and collapsed the panel. onOpenTree
   now calls stopImmediatePropagation.
6. Rows carried no agent id, making @<id> undiscoverable from the
   panel. Rows print the short id and the header shows "@id steer".
7. A background fan-out rendered as N live rows plus a batch row.
   dropCoveredBatches drops the batch record when its children are
   already present as live subagents.
8. applyAsyncList set a fresh array every 1.5s poll, re-rendering the
   TUI unconditionally. Guarded by a snapshot comparison.
9. The async_list RPC shipped whole records (context, goals, toolsets,
   session keys) for up to 50 entries. Projected server-side to the
   six fields the panel renders.

Non-blocking:

- appLayout.tsx no longer carries the no-op reformat; the diff there
  is a pure +5 addition.
- @<id> now has a /help entry and completion support.
- The steer text is echoed into the transcript, so there is a record
  of what was sent, not just "delivered → @id".
- agentsPanel tests render through Ink with renderSync and assert on
  the painted frame instead of walking the returned element tree.
@JoaoMarcos44
JoaoMarcos44 requested a review from OutThisLife July 25, 2026 19:31

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the substantial follow-up work; the bounded panel, projection, and real-Ink coverage address the earlier panel review well.

Problems

  • tools/delegate_tool.py:205-234 treats AIAgent.steer() returning true as a delivered steer. That method only queues _pending_steer (run_agent.py:3093-3127). If the child returns its final answer before another tool boundary, the runtime exposes the text as pending_steer (agent/turn_finalizer.py:615-620), but _run_single_child() builds its result from final_response and does not preserve it (tools/delegate_tool.py:2310-2419). The TUI can therefore show delivered for steering the child never sees.
  • The branch predates the RPC split: current main owns the adjacent delegation handlers in tui_gateway/methods_session.py:2773-2810 after f67ca22, rather than tui_gateway/server.py. This needs a deliberate salvage, not a direct application.

Suggested changes

  • Make the RPC reject or explicitly route a steer that cannot reach a future child decision point, and add a real child-final-response race regression.
  • Rehome the RPC work into the current methods module during salvage.

Automated hermes-sweeper review.

Comment thread tools/delegate_tool.py
@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
@JoaoMarcos44
JoaoMarcos44 requested a review from teknium1 July 31, 2026 13:32
Resolves the RPC split conflict flagged in review (NousResearch#70899): delegation.async_list,
subagent.send, delegation.send, and _project_async_delegation move from
tui_gateway/server.py into tui_gateway/methods_session.py, alongside where main
already relocated delegation.status/pause and subagent.interrupt (f67ca22).

Also merges the steer_fn/subagent_ids fields this branch added onto main's
progress_fn/stale-monitor bookkeeping in tools/async_delegation.py and
tools/delegate_tool.py — both sets of fields are independent and additive.
…C rehome

Addresses the two blockers from teknium1's review (NousResearch#70899):

- tools/delegate_tool.py:_run_single_child now checks result["pending_steer"]
  (turn_finalizer's leftover-steer handoff) and surfaces it as entry["missed_steer"]
  instead of silently dropping it. subagent.send/delegation.send can only report
  best-effort acceptance in real time — they can't know whether the child's current
  LLM call is its last (no future tool-call boundary to drain into). missed_steer
  is the correction once the true outcome is known: it rides the completion event
  (tools/async_delegation.py) and gets rendered into the re-injection block the
  model actually reads (tools/process_registry.py:_format_async_delegation), so a
  steer that never reached the child is visible instead of vanishing behind an
  uncontested "delivered: true".

- _project_async_delegation, delegation.async_list, subagent.send, and
  delegation.send move from tui_gateway/server.py into tui_gateway/methods_session.py,
  next to delegation.status/pause and subagent.interrupt (already relocated there by
  main in f67ca22). _project_async_delegation itself stays a plain helper in
  server.py — HandlerRegistry.install() only rebinds @method-decorated functions
  onto server.py's globals, so a bare helper defined in the split module is
  invisible to a handler running with server.py's globals as its __globals__.

Also merges main's stale-monitor/progress_fn additions in tools/async_delegation.py
and tools/delegate_tool.py with this branch's steer_fn/subagent_ids fields (both
sets are independent and additive), and drops the now-dead PasteSnippet/pasteSnips
references in useSubmission.ts — main superseded paste snippets with the inline-
attachments token system (expandTokens/tokensRef) while this branch was stale.

Regression coverage: _run_single_child surfaces/omits missed_steer correctly,
the completion event and its formatted re-injection text carry it end to end.
@teknium1

teknium1 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Update after maintainer review: we're taking this in, expanded. The plumbing here (stores, delegation.async_list/send RPCs, steer routing incl. the missed_steer race handling, replay snapshots) reviewed very well and will be salvaged with your authorship preserved. On top of it we're building out the presentation into a full tabbed "Mission Control" overlay (dashboard + live feed unified with the delegation live-transcript logs, timeline lanes, detail pane with budget meters, steer modal with quick actions, spawn tree, replay diff) — the docked strip stays as the ambient surface.

Leaving this PR open as the salvage base; it needs a rebase (currently CONFLICTING) which we'll handle during the build, and the committed infographic/*.png will be stripped from the branch. No action needed from you — great foundation, thanks.

@Xipong

Xipong commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The expanded Mission Control direction sounds excellent. A unified dashboard/live feed, timelines, budgets, steering, spawn tree, and replay view could be a genuine killer feature for Hermes.

This is not a request for the contributor to broaden this PR; it already addresses its stated TUI problem well. The question is for the maintainers at the product-architecture level: whether this Mission Control work and Hermes' existing Desktop/Dashboard subagent surfaces are intended to become one product or continue as separate implementations.

I want to ask about the intended product boundary before the TUI implementation becomes the de facto owner of subagent orchestration. #74375 was closed partly because Mission Control was identified as the natural home for future subagent model selection, but the scope announced here is currently TUI-specific and does not include configuring the persistent model/provider/reasoning defaults used by future children. Displaying a running child's model in the detail pane is not the same capability as choosing what the next child will run on.

Hermes also already has parallel pieces of this product:

  • Desktop has an Agents view across sessions with nested parent/child trees, live activity streams, model/duration/tool/token/file metadata, aggregate cost, and child-session watch windows.
  • Dashboard has its own model and delegation configuration surfaces, but no announced native Mission Control equivalent.
  • Plain CLI and Classic CLI users need a lightweight path to configure future-child defaults without launching and navigating a separate TUI surface.

If the full control set lands only here, the TUI becomes substantially more capable than Desktop and Dashboard for subagent orchestration, while those existing surfaces remain separate partial implementations. I cannot tell whether that divergence is intentional, temporary, or simply outside the announced scope.

Could the maintainers outline the intended direction?

  1. Is Mission Control a TUI-specific feature, or a shared subagent-control product with surface-independent state/RPCs and native TUI, Desktop, and Dashboard presentations?
  2. Are model/provider/reasoning selection for future subagents and the distinction between persistent spawn defaults versus controls for an already-running child part of the plan?
  3. Are the existing Desktop Agents view and Dashboard expected to receive equivalent steering, timeline, budget, replay, and spawn controls?
  4. What is the intended path for shell/Classic CLI users who do not run the TUI?

Even a rough roadmap or ownership boundary would help contributors avoid extending several increasingly divergent versions of the same product. Mission Control looks extremely promising; the missing piece is whether it is meant to unify Hermes' subagent experience across surfaces or make the TUI the one fully featured orchestration client.

@alt-glitch alt-glitch added comp/tools Tool registry, model_tools, toolsets and removed sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) labels Aug 1, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

One PR addresses #70894. #70899 substantially covers both reported causes with a bounded docked agents panel backed by async-delegation snapshots and live @id steering, including explicit handling of the terminal pending-steer race, but it remains a partial fix because the requested ^a path is absent and the branch requires conflict-aware salvage onto current main.

Related pull requests

Suggested consolidation

Keep open with a salvage path on #70899: preserve its reviewed RPC, store, panel, steering, missed_steer, replay, and test work while rebasing the conflicting branch onto current main, removing infographic/*.png, and integrating the planned Mission Control presentation. This follows the contributor's keep_open verdict and subsequent maintainer direction; there are no duplicate PRs to close.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I70894(["issue #70894 (open)"])
    P70899["PR #70899 (open)"]
    P70899 -->|best fix| I70894
    class I70894 open
    class P70899 open
    class P70899 best
    class P70899 target
    click I70894 "https://github.com/NousResearch/hermes-agent/issues/70894"
    click P70899 "https://github.com/NousResearch/hermes-agent/pull/70899"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 146 kB of PR diffs, 12 kB of issue/PR text, 13 kB of discussion (10 comments), 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tools Tool registry, model_tools, toolsets comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/delegate Subagent delegation type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Async Delegation View — docked agents panel + live steering (impl ready, PR linked)

6 participants