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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ loosely while pre-1.0 (breaking changes can land on minor bumps).

## [Unreleased]

- **Fleet dashboard pane (Phase C1).** The TUI consumes fleet SSE (`stream_id` +
`depth`) into an in-memory tree and paints a right-rail dashboard: depth
indent, agent, active tool, token totals, and a `◇` marker on reconcile JIT
workers. `Ctrl-w f` / click focuses the conversation pane bound to that
stream; Esc on a running row uses the existing in-flight cancel. Reconcile
`mode=ensure` waves show residual vs held plus the current wave in the same
pane. See [Fleet](docs/tui/fleet.md).

## [0.13.10] — 2026-09-21

### Fixed
Expand Down
27 changes: 24 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,11 @@ target_sources(arbiter PRIVATE
src/tui/sidebar.cpp
src/tui/sidebar_format.cpp
src/tui/history_sidebar.cpp
src/tui/fleet.cpp
src/tui/menu.cpp
src/tui/opentui/sidebar_frame.cpp
src/tui/opentui/history_sidebar_frame.cpp
src/tui/opentui/fleet_frame.cpp
src/tui/opentui/menu_frame.cpp
src/tui/opentui/pane_frame.cpp
)
Expand Down Expand Up @@ -921,6 +923,26 @@ if(INDEX_BUILD_TESTS)
)
add_test(NAME unit_sidebar COMMAND unit_sidebar)

# Fleet tree state machine: SSE/event → rows, JIT teardown, tokens.
add_executable(unit_fleet
tests/test_fleet.cpp
src/tui/fleet.cpp
src/tui/sidebar_format.cpp
src/model_catalog.cpp
src/model_context.cpp
src/styled_text.cpp
src/theme.cpp
src/tui/tui_design.cpp
src/json.cpp
)
target_include_directories(unit_fleet PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/third_party/doctest
)
target_compile_definitions(unit_fleet PRIVATE
ARBITER_THEMES_DIR="${CMAKE_SOURCE_DIR}/themes")
add_test(NAME unit_fleet COMMAND unit_fleet)

add_executable(unit_todo_resolve
tests/test_todo_resolve.cpp
src/todo_resolve.cpp
Expand Down Expand Up @@ -1848,7 +1870,6 @@ if(INDEX_BUILD_TESTS)
)
add_test(NAME unit_remote_connect COMMAND unit_remote_connect)

<<<<<<< HEAD
# JSON number grammar (incomplete frac/exp). Separate from #342's
# unit_json (surrogate pairs in parse_string) so the two PRs do not
# share tests/test_json.cpp or the CMake insert after unit_logger.
Expand All @@ -1861,7 +1882,7 @@ if(INDEX_BUILD_TESTS)
${CMAKE_SOURCE_DIR}/third_party/doctest
)
add_test(NAME unit_json_numbers COMMAND unit_json_numbers)
=======

# Unary --connect DELETE/PATCH body cap. Separate binary so
# unit_remote_connect stays network-free; this one stands up a
# loopback server against RemoteApiClient's one-shot curl handles.
Expand All @@ -1882,7 +1903,6 @@ if(INDEX_BUILD_TESTS)
Threads::Threads
)
add_test(NAME unit_remote_http COMMAND unit_remote_http)
>>>>>>> origin/main

# tui_design.cpp includes themes_data.h — wire every unit target that
# compiles it to the embed generator (same header as the main binary).
Expand All @@ -1902,6 +1922,7 @@ if(INDEX_BUILD_TESTS)
unit_conversation_titling
unit_message_replay_roundtrip
unit_remote_connect
unit_fleet
)
arbiter_use_embedded_themes(${_t})
endforeach()
Expand Down
6 changes: 3 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,16 @@ calendar commitments.
### Phase 5 — Multi-agent mission control
- [x] **Intent engine foundations-** Hybrid classify/route before dispatch (heuristic, then advisor-model LLM); seed slots for plans/todos; fail-open into index; `POST /v1/intent` + SSE `intent` event
- [x] **Intent reconcile (Phase A)-** `POST /v1/reconcile`: compile `target_state` + invariants into a state contract, observe a bound workspace, require verification, optional snapshot rollback; `@arbiter/sdk` `IntentClient`; durable `reconcile_runs` + SSE
- [x] **JIT ΔS waves (Phase B / #208)-** `mode=ensure` wave loop: resolve `agent_map` / clause.agent / capability fallback, spawn ephemeral clones for active ΔS only, re-observe as success proof, teardown before the next wave; budgets `max_waves` / `max_wall_ms` / `max_agents_per_wave` / `max_retries_per_clause`; SSE `agent.spawned` / `agent.teardown`. Observe mode and `/v1/orchestrate` unchanged. Fleet UI remains Phase C
- [x] **JIT ΔS waves (Phase B / #208)-** `mode=ensure` wave loop: resolve `agent_map` / clause.agent / capability fallback, spawn ephemeral clones for active ΔS only, re-observe as success proof, teardown before the next wave; budgets `max_waves` / `max_wall_ms` / `max_agents_per_wave` / `max_retries_per_clause`; SSE `agent.spawned` / `agent.teardown`. Observe mode and `/v1/orchestrate` unchanged
- [x] **Always-on presence-** Constitution `presence.mode: always_on`; a pair colleague looks over a peer's shoulder after each tool batch and may inject `[PRESENCE: …]` context. Fail-open; cannot halt. SSE `presence` + TUI `◎ presence`. Opposite lifetime of JIT (#208).
- [ ] **Fleet dashboard pane-** Live tree of depth, agent, tools, tokens; click-to-focus/Ctrl-W bindings
- [x] **Fleet dashboard pane-** Live tree of depth, agent, tools, tokens; click-to-focus / `Ctrl-w f` ([docs](docs/tui/fleet.md))
- [ ] **Plan to execution observability-** Planner plans as first-class objects with progress against todos
- [ ] **Delegation policies-** Consitutions declare max depth, allowed callees, budget caps (tokens/$)
- [ ] ~~**Workflow recipes-** Checked-in “crews” (JSON): ordered/parallel graphs of agents + shared todo board~~
- [ ] **Advisor policy packs-** Reusable gate profiles (strict / coding / research)

**Acceptance criteria:**
- [ ] A user can watch and steer a 5-agent job.
- [x] A user can watch and steer a 5-agent job.

### Phase 6 — 1.0
- [ ] **Security defaults pass-** Safer TUI exec prompts; clearer sandbox docs; threat model refresh in SECURITY.md
Expand Down
1 change: 1 addition & 0 deletions docs/concepts/fleet-streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ The master orchestrator's provider deltas stream live (same as sub-agents). Inco
## See also

- [SSE event catalog](sse-events.md)
- [TUI fleet dashboard](../tui/fleet.md)
- [`POST /v1/orchestrate`](../api/orchestrate.md)
1 change: 1 addition & 0 deletions docs/concepts/sse-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Spec-compatible A2A clients hit [`POST /v1/a2a/agents/:id`](../api/a2a/dispatch.

- [A2A protocol](a2a.md) — the Agent2Agent counterpart to this catalog.
- [Fleet streaming](fleet-streaming.md)
- [TUI fleet dashboard](../tui/fleet.md)
- [Advisor](advisor.md) — gate signal grammar, modes, redirect budget.
- [Presence](presence.md) — always-on peer review and mid-turn injection.
- [Intent](intent.md) — pre-dispatch classify/route.
Expand Down
1 change: 1 addition & 0 deletions docs/tui/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Each pane is an independent conversation view. See [panes.md](panes.md) for the
| `Ctrl-w c` | Close the focused pane. The pane's exec thread is joined cleanly; in-flight turn is cancelled. |
| `Ctrl-w t` | Toggle the conversation-history sidebar (left rail). |
| `Ctrl-w b` | Enter the sidebar to pick a prior conversation or start a new one. |
| `Ctrl-w f` | Show the fleet dashboard (live multi-agent tree) and enter it. |

## Background loops

Expand Down
74 changes: 74 additions & 0 deletions docs/tui/fleet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Fleet dashboard

The fleet pane is a live tree of every agent turn in the current job — master, `/agent` / `/parallel` children, and reconcile JIT workers. It consumes the existing [fleet streaming](../concepts/fleet-streaming.md) SSE model (`stream_id` + `depth`); it is not a second orchestrator.

Presence (`◎ presence`) is the opposite lifetime: residents stay in the room. Fleet rows are request-scoped and go away when the job ends (JIT clones disappear on `agent.teardown`).

## When it appears

The pane occupies the **right rail** (~24–28 columns) once a job has at least one stream, the same width breakpoint as the session sidebar (96 remaining columns). Unlike the session sidebar, it **stays up in multi-pane layouts** — that is when you need it.

Idle sessions keep the rail free so conversation panes are not crowded. An empty Fleet box is not shown unless you force focus with `Ctrl-w f`.

When both Fleet and Session want the rail (single pane, wide terminal, a job is live), Fleet stacks on top and Session sits below.

## What each row shows

```
╭ Fleet ──────────────╮
│ ensure · wave 2 │
│ Δ 3 residual · 2 held│
│ │
│ ● index fetch│
│ ├ ● researcher_a 1.2k│
│ └ ◇ writer │
╰─────────────────────╯
```

- **Indent** follows `depth` (0 master, 1 delegated, 2 sub-sub). The depth cap is 2; the tree does not invent a third level.
- **Status:** `●` running, `✓` ok, `✗` failed, `○` opened but not yet started.
- **Name** of the agent. Reconcile JIT workers get a `◇` marker (ephemeral clone).
- **Active tool** while a `/cmd` is in flight; otherwise **token totals** for that stream.

The overlay at the top is reconcile-only: `mode=ensure` residual vs held clause counts and the current wave, next to spawn/teardown rows. The pane does not run reconcile (that is still `POST /v1/reconcile`); it only displays events.

## Steer / focus

Selecting a row focuses the conversation pane bound to that stream so you can watch its output. Bindings follow the existing pane / sidebar conventions:

| Input | Effect |
|-------|--------|
| `Ctrl-w f` | Show the fleet pane and enter it (second press hides). |
| Click a fleet row | Select that agent and focus its pane. |
| `↑` / `↓` (or `k` / `j`) | Move the selection (while fleet is focused). |
| `Enter` | Focus / open the pane bound to the selected stream. |
| `Esc` | On a **running** row: cancel that pane's in-flight turn (existing kill-switch). Otherwise leave fleet focus. |
| Wheel over the tree | Scroll the list. |

`/parallel` children stream into the **host** pane (interim `→ agent` headers). Selecting them focuses that host — the dashboard does not auto-split a pane per child (that would drown the layout). `/pane` spawns already have their own leaf; selecting that agent focuses the spawned pane.

Cancel does **not** invent a per-stream kill protocol. Esc maps to the same `cancel_pane_turn` path as interrupting a focused conversation pane (local `CancelToken` or remote `POST /v1/requests/:id/cancel`).

## Event → tree

| Event | Tree |
|-------|------|
| `stream_start` | Open a row (`stream_id`, `depth`, agent). |
| `agent.spawned` | Open a JIT row (`clone_id`, `◇`). |
| `agent_start` / `text` | Mark running. |
| `tool_call` | Set / clear the active tool. |
| `token_usage` | Add input + output on that stream. |
| `reconcile.delta` | Overlay: residual, held, wave. |
| `stream_end` | Mark ended; keep the row until the next job. |
| `agent.teardown` | Remove the JIT row immediately. |
| `done` / new `request_id` | End leftovers / clear the previous job. |

See [SSE events](../concepts/sse-events.md) and [Fleet streaming](../concepts/fleet-streaming.md).

## See also

- [Panes](panes.md) — split / focus / `/pane`
- [Keybindings](keybindings.md)
- [Streaming](streaming.md)
- [Presence](../concepts/presence.md) — always-on residents
- [Reconcile](../concepts/reconcile.md) — JIT ΔS waves
3 changes: 2 additions & 1 deletion docs/tui/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Rendering uses [OpenTUI](https://github.com/anomalyco/opentui) (native cell-buff

A pane is to a conversation what a tab is to a browser. A new pane is a new conversation against an agent of your choosing; multiple panes run side-by-side or stacked, each independently typing, streaming, and waiting for its agent. Background loops (long-running agent processes) live alongside; foreground panes can spawn child panes (`/pane <agent> <msg>`) whose results land back in the spawner when done.

Start with `arbiter`. The default layout is a single pane covering the main terminal area. A **left-hand conversation sidebar** lists prior threads and lets you start a new one (`Ctrl-w b` to enter, `Ctrl-w t` to toggle visibility). A **right-hand sidebar** shows session usage, agent/model, todos, and recent tool activity when the terminal is wide enough (MCP appears only after MCP tools are used); `Ctrl-w s` toggles it. `Ctrl-w v` / `Ctrl-w h` split the main area.
Start with `arbiter`. The default layout is a single pane covering the main terminal area. A **left-hand conversation sidebar** lists prior threads and lets you start a new one (`Ctrl-w b` to enter, `Ctrl-w t` to toggle visibility). A **right-hand sidebar** shows session usage, agent/model, todos, and recent tool activity when the terminal is wide enough (MCP appears only after MCP tools are used); `Ctrl-w s` toggles it. A **fleet dashboard** (`Ctrl-w f`) occupies that same right rail while a multi-agent job is running — live tree of depth, agent, tool, and tokens; it stays visible with several conversation panes. `Ctrl-w v` / `Ctrl-w h` split the main area.

## Screen anatomy

Expand Down Expand Up @@ -55,6 +55,7 @@ Token totals also appear in the pane header stats row (right side of row 1) when
- **[Slash commands](commands.md)** — the full `/cmd` catalogue, grouped by category.
- **[Keybindings](keybindings.md)** — every key, chord, and modifier the editor recognizes.
- **[Panes](panes.md)** — multi-pane layouts: split, focus, close, `/pane` spawn semantics.
- **[Fleet](fleet.md)** — live multi-agent tree: depth, tools, tokens, click-to-focus.
- **[Streaming](streaming.md)** — what you see during a turn: thinking spinner, tool-call indicator, verbose mode, cancellation.
- **[Sessions](sessions.md)** — global conversations, autosave / mid-turn checkpoints, compaction.
- **[Output UX](output-ux.md)** — tool timeline, thinking rows, permission cards, replay chrome.
Expand Down
13 changes: 13 additions & 0 deletions docs/tui/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Arbiter ships its own line editor. Bindings below are the complete set the edito
| Wheel | Scroll pane / history list under the pointer |
| Drag split gutter | Resize adjacent panes |
| Left-click history row | Switch to that conversation |
| Left-click fleet row | Focus the pane bound to that stream |
| Right sidebar | Ignored (display-only) |

Disable with `"mouse": false` under `layout` in `~/.arbiter/tui.json`.
Expand Down Expand Up @@ -85,9 +86,21 @@ cards. See [output-ux.md](output-ux.md).
| `^W c` | Close the focused pane. Its exec thread is joined; in-flight turn is cancelled. Last remaining pane cannot be closed. |
| `^W t` | Toggle the conversation-history sidebar (left rail). Preference is saved to `~/.arbiter/tui.json`. |
| `^W b` | Enter the conversation sidebar for selection. Auto-shows the sidebar if hidden. Use `↑`/`↓` and `Enter`; `Esc` cancels. |
| `^W f` | Show the fleet dashboard and enter it. Second press hides it. Click a row or `Enter` to focus that stream's pane; `Esc` on a running row cancels that pane's turn. |

If the chord byte doesn't match any of the above, it's silently dropped — the editor returns to the normal input state. `^W` followed by a regular character does *not* fall through to insertion; the chord window always consumes its byte.

## Fleet dashboard (when focused via `^W f` or a click)

| Key | Action |
|-----------|--------|
| `↑` / `↓` | Move selection (`j` / `k` aliases). |
| `Enter` | Focus the conversation pane bound to that stream. |
| `Esc` | Cancel the selected in-flight turn if it is running; otherwise leave fleet focus. |
| `PgUp` / `PgDn` | Page the tree. |

See [Fleet](fleet.md).

## Conversation sidebar (when focused via `^W b`)

| Key | Action |
Expand Down
1 change: 1 addition & 0 deletions docs/tui/panes.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ When `layout.mouse` is enabled in `~/.arbiter/tui.json` (the default), the TUI e
| Wheel over scrollback | Scroll that pane (does not steal keyboard focus) |
| Left-click a conversation in the history sidebar | Select and switch to it |
| Drag a split gutter | Resize the two adjacent panes asymmetrically |
| Left-click a fleet row | Focus the conversation pane bound to that stream |
| Right sidebar | Display-only — clicks and wheel over it are ignored |

Set `"layout": { "mouse": false }` to keep keyboard-only input (useful inside tmux without `set -g mouse on`, or when the host terminal fights with mouse capture). Text selection copy uses OSC 52; under tmux enable `set -g set-clipboard on` (or an equivalent clipboard passthrough).
Expand Down
2 changes: 1 addition & 1 deletion docs/tui/streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This indicator runs in the output pump and updates at ~80 ms cadence. It clears

Model deltas land in the scroll region as they arrive via `StreamRenderer`: `BlockParser` suppresses `/cmd` lines (unless `/verbose`) and forwards unambiguous incomplete prose immediately, `MarkdownRenderer` commits styled spans on newline and exposes a replaceable **live tail** for the in-progress line so tokens paint word-by-word, non-diff fenced code streams into `CodeSegment` (draw-time collapse — first eight body lines plus a live `… +N lines` summary, no placeholder artifacts in scrollback), and ` ```diff ` patches become `DiffSegment` panels (full-width for file adds/deletes; split for edits) with a `Patch #N` action line. Streamed patches **pause the model stream** on an interactive review card (↑↓/Enter, or `[a]`/`[r]`/`[A]`/Esc; PgUp/PgDn scrolls). Use [`/diff`](commands.md) for manual review of remaining pending patches, `/accept-edits` to keep auto-applying, or `/diff apply` to write immediately under the conversation workspace directory (creates missing files; exact hunk-header match; `/diff undo` restores when unchanged). Styling resolves from `tui.json`'s `content` tokens through `StyleId` → OpenTUI highlights — no ANSI round-trip on the master stream path. User echoes, advisor/loop/presence banners, and interrupt markers use the same prose path. Always-on `CONTEXT` notes paint as `◎ presence · <watcher>`; `SILENT` reviews stay quiet. Vertical rhythm (`block_gap` / `panel_gap` / `prose_paragraph_gap`) and panel surfaces (`code_bg`, `diff_bg_*`) are theme tokens — see [themes.md](themes.md).

Sub-agent progress uses the same pipeline with `kInterim` policy: dimmed prose, collapsed fences, capped at eight display rows / 480 columns. The master's synthesis turn carries the full result.
Sub-agent progress uses the same pipeline with `kInterim` policy: dimmed prose, collapsed fences, capped at eight display rows / 480 columns. The master's synthesis turn carries the full result. The [fleet dashboard](fleet.md) (`Ctrl-w f`) is the live tree of those streams — depth, agent, active tool, tokens — so you do not have to read raw SSE.

### 3. Tool calls

Expand Down
Loading
Loading