Move agent and conversation authority into truthful footer controls, keep external sessions observed-only, and give recent server conversations an explicit picker.\n\nProject itinerary state into a persistent ribbon, invalidate it on every itinerary mutation, and keep failures in the transcript while successful route chatter yields to the ribbon. Long tool returns now scroll in place.\n\nThe SAF now owns the full Panel contract and parity boundary; the abandoned green observer card is removed.
10 KiB
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:
- the conversation occupies the primary, scrollable field;
- transient subconscious activity may touch the lower edge of that field;
- the itinerary remains as a thin persistent ribbon above the composer while a route exists;
- the composer owns text entry, send/cancel/interject behavior, attachment state, and commands;
- 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.
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. A filename chip connected only to an old Gemini path is not Souveraine multimodality. Until the substrate accepts and persists the input, the Panel must call the feature unavailable rather than imply delivery.
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.
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.
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:
- Source: Rust formatting, QML parsing, lint, manifest coverage, and focused projection tests are clean.
- CI: Gitea compiles and tests the Rust/QML package path on ArchDev and emits the versioned artifact.
- Installed: the package or development compose points at the intended source revision and QuickShell loads it without binding or component errors.
- 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.