Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/saf/surface/04-agent-pane.md
Fimeg bde961c6f2 saf: one spine — device, state, and work under the index
PAF becomes saf/device (history kept), STATE.md dissolves
into saf/state.md with the dated era archived, the substrate
SAF moves up from souveraine, and every agreement points at
saf/INDEX.md and nowhere else. one map, nothing to remember
2026-08-18 09:47:30 -04:00

246 lines
14 KiB
Markdown

# The agent panel
The Panel is Souveraine's close conversation surface on the desktop. It is not
a generic provider picker laid over somebody else's chat client. The agent in
its footer is the agent this pane can address; the transcript is the substrate's
typed conversation; thought, action, itinerary, subconscious work, and context
pressure remain distinct even when they share a narrow column.
The present shell code is split across
`surfaces/quickshell/services/Souveraine.qml`, the `Ai.qml` compatibility
adapter, and `modules/souveraine/agent/`. The outer list and composer still live
in the borrowed `modules/ii/sidebarLeft/AiChat.qml`. That is a migration seam,
not permission for the borrowed component to become a second owner of state or
product meaning.
## The anatomy
The vertical order is deliberate:
1. the conversation occupies the primary, scrollable field;
2. transient subconscious activity may touch the lower edge of that field;
3. the itinerary remains as a thin persistent ribbon above the composer while
a route exists;
4. the composer owns text entry, send/cancel/interject behavior, attachment
state, and commands;
5. the footer holds compact controls for the agent and the few secondary
surfaces that actually work.
The agent menu opens from its footer chip. It does not live above the
conversation as permanent green furniture. The itinerary does remain above the
composer because it answers a different question: not which process happens to
be alive, but where the current agent is in the work Casey asked her to carry.
Every footer chip must either expose a state the pane owns or perform the act it
advertises. A decorative tool selector is worse than no selector when tools are
configured in the agent's sensorium and cannot be switched here. Remove it.
Commands may still name unavailable work in documentation; they do not earn a
button on the live surface until their path exists.
## One controlled agent, other sessions observed
`Souveraine.currentAgentId` is the controlled position. The selector is
populated from `/v1/agents` through the Souveraine service and may switch only
between those agents. A running turn pins that selection until it finishes or
is cancelled, because changing the face while a response is arriving would
mislabel both transcript and authority.
Codex and Claude processes discovered by `AgentSessions` are not models behind
this pane. Their status may appear under a separate **observed only** heading in
the opened footer menu, with provider and session label, but those rows do not
accept selection and never imply that the composer can steer them. Observation
and control can share a menu only when the boundary is visible in words and in
behavior.
[Agent presence](05-agent-presence.md) owns the collector contract, provider
health, activity heuristics, and the future path toward one session service.
This chapter owns only how that projection enters the conversation surface.
The Panel eventually needs richer active-agent status: substrate reachability,
conversation attachment, primary/subconscious phase, queued interjections, and
the last terminal event. Those facts belong to the Souveraine connection, not
to heuristics over terminal processes.
## A transcript is typed
The substrate emits typed segments. The Panel preserves them:
- assistant speech is ordinary message content;
- reasoning is Thinking and renders in `ThinkingCard`;
- tool invocation and return are one correlated `ToolCard`;
- interstitial text is visibly provisional rather than final speech;
- system/interface notices are not forged as the agent's voice.
Reasoning may be retained for audit, reflection, and context accounting without
being replayed to a generic model as `[Reasoning]:` assistant prose. Storage,
model replay, and visible rendering are three projections of the same typed
record. None may flatten Thinking into speech merely because a provider lacks a
native reasoning input block.
Tool cards collapse after success, stay open while running or failed, and show
status without requiring expansion. Their payload is selectable and scrolls
inside a capped body; a long grep or build result must not be clipped at 240
pixels or stretch one message across the entire conversation. The inner tool
body consumes scrolling while it has overflow. Outside it, the transcript
keeps the wheel.
Tool output is evidence. It may be summarized in the header, but the original
return remains available until transcript retention or compaction explicitly
changes it.
## The itinerary is state, not chat debris
`memory/system/dynamic/itinerary.md` is the canonical route. The Panel does not
parse that file and does not keep a second QML itinerary that dies on reload.
The authenticated `GET /v1/agents/:id/itinerary` endpoint projects its title,
current stop, statuses, descriptions, linked todo ids, nature, and energy.
An itinerary stream event is an invalidation edge. On set, advance, or clear,
the service refreshes that projection. Empty is meaningful: it removes the
ribbon. A shell reload or agent switch asks the substrate again, so current
work survives the surface without turning surface memory into authority.
The compact ribbon shows the title, current stop, phase, progress, and stale
state. Opening it reveals a bounded scrollable list of stops and the todo
metadata the substrate supplied. A successful itinerary tool card is hidden
from the transcript because the persistent ribbon already carries that state;
an itinerary failure remains visible because failed action is evidence.
The itinerary is not the todo ledger. It may reference a todo, but clearing a
route does not erase a commitment and completing a route does not silently
invent one.
## Conversation life
Conversation identity lives on the server. A shell reload may offer the latest
conversation but must not silently attach it unless the configured policy says
so. New, resume, list, filter, and fork all act on server conversation ids; a
local array of bubbles is a projection, never the canonical thread.
The complete Panel needs these conversation acts on glass:
- a clearly named new conversation;
- an explicit resume offer with enough metadata to distinguish the thread;
- a filterable recent-conversation list, with indexed content search as the
larger corpus demands it;
- fork from a selected message or turn boundary;
- visible attachment to a conversation id without exposing token material;
- honest recovery when the shell reattaches to an active stream.
The current adapter carries server resume and active-stream reattachment. The
footer conversation menu now exposes new, continue, recent-thread browsing,
and a local id/date filter. Indexed content search and fork remain parity work
until their owned Panel components and server paths are connected. A slash
command is not the finished interaction merely because it reaches the same
function.
## Composer and input
Send creates a user turn only when no turn is active. During a turn the same
space must make cancel and queued interjection legible rather than accepting
text whose delivery phase is unknowable. `202 Accepted` for an interjection
means queued, not read; the surface keeps that distinction until the next
round boundary or terminal event.
Paste and attachment parity requires a typed input envelope: text, image or
file identity, media type, size, provenance, and any transformation performed
before send. The substrate HTTP boundary, provider path, persistence, and TUI
now accept image blocks. The Panel does not: its QML request still sends a
plain string and transcript restore reduces an image to `[image]`. A filename
chip connected only to the old Gemini path is not Souveraine multimodality.
Keyboard, touch, and pointer must all reach the same acts. Focus returns to the
composer after a menu choice; Page Up and Page Down move the transcript; tool
payload scrolling and text selection do not accidentally send or collapse a
card. Small icon-only hit targets need tooltips and at least the shell's normal
touch target.
## Command surface audit, 2026-08-17
The borrowed composer still advertises illogical-impulse commands. A command
name surviving in `AiChat.qml` does not mean Souveraine implements the act.
| Command | Current truth | Panel direction |
|---|---|---|
| `/agent` | real: selects from `/v1/agents`, guarded while a turn runs | keep as the textual form of the footer agent control |
| `/resume` | real for the selected agent's latest thread; the footer menu also lists and loads recent ids | keep, add `/resume <id>` only if it uses the existing server load path |
| `/new` | real: clears the projection and detaches so the next send creates a server conversation | keep; the footer conversation control is the discoverable form |
| `/attach` | stale refusal: image blocks now cross the substrate; QML still sends only text and restores images as a marker | wire a typed image envelope, preview/removal, capability refusal, and image restore |
| `/btw` | absent in the Panel; the server already exposes conversation fork and the TUI owns the fork lifecycle | build a separate fork stream and bounded visual overlay with dismiss and jump |
| `/model` | aliases `/agent`; the objects called models are Souveraine agents | remove the alias until a real per-agent model inspection/change API exists |
| `/tool` | fixed fake vocabulary of one and an explanatory refusal | remove; tools are an agent sensorium, not a composer mode |
| `/prompt` | prints ownership advice; no prompt files or mutation path are connected | remove as a setter; a future inspector must read the composed server fact |
| `/key` | prints auth advice and never reads or writes a key | remove; credentials need a secure settings/auth path |
| `/save` | writes a local JSON bubble snapshot that drops typed segment fidelity | rename as export only if kept; it is not conversation persistence |
| `/load` | restores that local projection, then starts a fresh live thread on send | remove from the live-thread vocabulary; server resume is the honest act |
| `/temp` | prints advice because temperature is agent configuration | remove until a typed settings path exists |
| `/test` | injects a development markdown fixture into the transcript | keep behind development tooling, not in the shipped command list |
The short implementation order is attachment, command pruning, then `/btw`.
Attachment is no longer a substrate project: the missing work is the Panel's
typed send and render path. Command pruning makes every advertised act true.
`/btw` is larger because it introduces a second concurrent conversation
projection; it needs its overlay before the verb is exposed.
Ordinary typing during a running turn remains an **interjection** into that
turn. It is queued and read at a round boundary. `/btw` is a **fork** with its
own conversation id and stream. Those gestures may look adjacent at the
composer and must never share state or wording.
## Context, reflection, and parity
Feature parity means parity with Souveraine's domain, not a pixel copy of the
TUI and not inherited buttons from ii. Both surfaces should be able to expose:
- actual used tokens, model limit, and pressure as separate values;
- the memory budget and the compaction that changed it;
- primary completion followed by subconscious activity;
- reflection/audit records without leaking them into agent speech;
- the current itinerary and commitment links;
- correlated tool calls, returns, duration, and failure;
- server health, cancellation, interjection, and reconnection;
- conversation creation, discovery, resume, and fork;
- typed multimodal inputs once the substrate owns them.
These may open as drawers, menus, expanded cards, or a dedicated secondary
Panel. They must consume the same server facts. The TUI is useful evidence of a
working domain action; it is not a second specification and its screen-local
state does not become the QML contract.
## Ownership migration
The borrowed `AiChat.qml` currently owns list lifecycle, focus, and composer
geometry. Souveraine-owned `AgentMessage`, `ThinkingCard`, `ToolCard`,
`AgentPaneMenu`, and `ItineraryRibbon` already own the semantics drawn inside
it. The next structural cut is an owned Panel host and conversation viewport
that consume the same service without copying the adapter's state.
Moving the Panel out of the resident shell is a separate lifetime boundary,
not a reason to fork its conversation model. [Surface lifetimes](06-surface-lifetimes.md)
owns that extraction and its memory/fault proof.
That cut should be rehearsed beside the live pane, then swapped once. A QML
write-reload loop over the only working chat surface turns each syntax fault
into a session-wide interruption and can abandon a live turn. Candidate and
live panes may share a server and a conversation only if one is read-only;
they must never become concurrent writers disguised as visual testing.
## Proof boundary
Panel work has distinct proof layers:
1. Source: Rust formatting, QML parsing, lint, manifest coverage, and focused
projection tests are clean.
2. CI: Gitea compiles and tests the Rust/QML package path on ArchDev and emits
the versioned artifact.
3. Installed: the package or development compose points at the intended source
revision and QuickShell loads it without binding or component errors.
4. On glass: the footer menu controls only Souveraine agents; observer rows are
inert; a long tool body scrolls; set/advance/clear updates the ribbon; a
failed itinerary remains in chat; switching, resume, cancel, and interject
have the states their labels claim.
A parsed QML file is not on-glass proof. A green Rust job is not an installed
surface. A hot reload that leaves the previous pane drawn is not success. Keep
those borders sharp; the Panel is where hidden ambiguity becomes something
Casey has to touch.