13 KiB
TASK 72 — the surfacing pane: tool primitives, status, and an honest bottom bar
Status: interim bridge composed and QuickShell restarted, 2026-08-11.
Patches 0002 and 0003 are applied on souveraine/primary; 0004 fixes their
QML compatibility. The running instance logged Configuration Loaded. This
proves composition and startup only: a live agent turn containing a tool call
has not yet exercised the new cards.
2026-08-12 — steps 1 and 2 committed, source only.
surfaces/quickshell/modules/souveraine/agent/ now exists and is owned:
ToolVocabulary (all 19 registry tools — kind, icon, summary, transcribed
from src/ui/chat/tool_renderers.rs rather than reinvented), ToolCard,
ThinkingCard, AgentMessage. Commits 3def292 and 6d0f891.
The gap that motivated it, measured: the vendor ToolCallBlock names 8
tools; the registry holds 19. The 11 it cannot name are precisely the
interiority verbs — outfit, nickname, subagent, atmosphere, reach,
consult, itinerary, todo, schedule, halt, intrusive. Filesystem
verbs rendered richly; every verb about being someone rendered as an
anonymous grey block.
MessageTextBlock / MessageCodeBlock are deliberately still imported from
the vendor snapshot. They are markdown renderers, not agent vocabulary; the
ii-base rule forbids landing agent-surface features there, not importing a
markdown engine we already have.
Evidence, labelled per this task's own instruction:
- source committed — yes, pushed to
gitea/primary. - composed — no. New files plus MANIFEST lines are inert until
deploy.shruns. Nothing imports the module. - candidate activated — no. The activating edit (
delegate: AiMessage→AgentMessage, plus one import) is queued assurfaces/quickshell/patches/0008, applies clean against HEAD, one line to revert. - real tool round rendered — no, and not claimed.
Gate: Qt6 qmllint (/usr/lib/qt6/bin/qmllint — the /usr/bin one is Qt5
and silent on every input), run against a staged copy of the composed tree so
qs.* resolves. Zero syntax findings across all four files. Residual warnings
are the widget-resolution class; the vendor AiMessage produces 38 of the
same family against 46 here, which is order-parity for a file that draws two
additional card types. The gate was proven able to fail on a deliberately
broken input.
Still step 2b, not done: AgentPaneHost / AgentChat do not exist yet, so
the sidebar still owns list lifecycle. The 0003 ii-base modifications are
therefore not deleted — parity has not been proven by a load, and this
task says not to call that complete from a clean startup alone.
Known parity gaps carried deliberately into the owned delegate: in-place message editing (Ctrl+S) is not reimplemented, and the attached-file indicator is not drawn — it belongs with the image-intake half, which has no picker yet either. Source: Casey, this morning. Adjacent: TASK-70 (the island — same renderer, different surface), TASK-65 (subconscious stall wedges the surface), TASK-59 (her face).
Why this is one lane and not seven papercuts
Every defect found in the 2026-08-10 night session had the same shape: the state arrives and nothing draws it. Tools and thoughts render with identical weight; atmosphere is live and undrawn; per-turn cost numbers serialise into the message and nothing shows them; todos and itinerary have no surface; subconscious progress has no indicator; used-context is absent while set-context is shown; image intake is missing at the UI end though the read sensor already lifts images fine.
The root of the rendering half is now known and patched (patch 0002): the server
emits typed events — assistant / reasoning / tool call / tool return — and
the shell flattens them into one markdown string, marking reasoning with
literal <think> fences. The channel is structural on the wire and becomes
punctuation at the surface. That flattening is what this task removes.
The ask (Casey's words, expanded)
- Tools as distinct UI primitives, not text. Separate visual identities for: sensors (read/glob/grep/list_dir), bash, file edit/write, memory, thinking, and outcome (success/failure).
- Collapse by default, expand on demand. A few kinds Casey wants visible inline; most should be a one-line snippet that opens.
- Status indication — running / done / failed, per call, visible without reading the payload.
- The bottom bar is currently wasted: it reads "souveraine souveraine" plus a subconscious icon. Those slots should carry live, useful state.
- The primary pane stays conversation. Everything else earns a tab.
Prior art we already own
The TUI has per-tool visual styles — bash and file edits look different — and this is the single largest transcription win available. Same project, same event stream, solved once already. Read the TUI's renderer before designing anything new.
Structural precondition
Stop flattening. The delegate should consume the typed event list, not a markdown blob. Once segments are first-class:
- the think-fence collision cannot recur (it is a string-level bug),
- per-tool delegates become a dispatch on segment kind,
- collapse/expand is per-segment state rather than text surgery.
Patch 0002 is a correct stopgap on the wrong layer. Do not treat it as the fix; treat it as the thing that keeps the surface readable until the layer moves.
Current bridge — useful now, deliberately not the destination
Patch 0003 carries the wire's assistant/reasoning/tool-call/tool-return kinds in a per-message segment list. The sidebar dispatches those segments directly; tool returns bind to calls by wire id (with a fallback for old id-less frames), and the new cards give bash, file, sensor, and memory calls compact summaries, folded output, and explicit running/done/failed state. Patch 0004 removes object-spread expressions which the installed QML runtime rejected at load.
That bridge is live because Casey needed the panel usable before a larger
rewrite. It is architectural debt, not permission to keep building there:
0003 modifies ii-base, while surfaces/quickshell/II-BASE.md and the shell
decision say Souveraine work belongs in original modules, not in the ii vendor
snapshot. No further agent-surface feature should land in ii-base.
Owned destination — a reloadable agent-surface package
Reasoned design; implement and prove it in this order. The chat surface
becomes an original Souveraine module, for example
surfaces/quickshell/modules/souveraine/agent/, with a small, legible split:
AgentPaneHostowns lifecycle and the stable pane boundary.AgentChatandAgentMessagerender typed segments only.ThinkingCard,ToolCallCard,PasteIndicator, andInProgressIndicatorare explicit primitives; tool cards own their collapsed state and status.services/Ai.qmlremains the data/substrate boundary. It preserves wire segment type and call identity, but it does not choose pixels. The old flatcontentprojection exists only for compatibility consumers such as copy and speech; no renderer may re-parse it.
This is the path away from ii: first host the owned module inside the existing
Souveraine compose, then replace the borrowed AiChat host, then remove this
bridge and its ii-base changes. TASK-70 remains a separate surface which may
reuse the renderer but does not own the sidebar's lifecycle.
Pane-local update and recovery
The desired operation is not "edit a live QML file and hope QuickShell
notices." An installed candidate version loads beside the active pane. Only
after its ready signal and basic data binding succeed does AgentPaneHost swap
the visible loader; a failed candidate leaves the previous pane intact. Expose
that as a narrow IPC action such as agentPane.reload, with status and the
active version visible in the host. The first implementation must be proven in
an isolated config before it is offered as a daily control. This is reasoned,
not yet hardware-proven.
Package boundary
The Rust substrate package stays a substrate package. Add a surface package
family which installs immutable, versioned QML/assets under
/usr/share/souveraine/surfaces/, with a small common package and explicit
desktop/phone profile packages as needed. A user-level launcher starts
QuickShell by explicit qs --path, rather than treating a mutable
~/.config/quickshell compose as the product. Package upgrade stages a new
candidate; it does not silently restart, swap, or claim the UI adopted it.
Activation is explicit, reversible, and leaves the last working candidate
available.
The existing deploy.sh composer is a development bridge: it copies ii-base
to ~/.config/quickshell/ii, symlinks Souveraine-owned manifest files into
~/.config/quickshell/souveraine, and does not restart QuickShell. It is not a
surface package manager.
Delivery sequence and proof
- Transcribe the TUI's tool semantics into the owned module, including the paste flag and honest in-progress state; exercise one real tool turn of each major kind.
- Replace the sidebar's borrowed chat host without changing the wire/data boundary; delete the 0003 ii-base modifications once parity is proven.
- Add the candidate pane loader and prove a bad candidate leaves the old pane drawn and usable.
- Package the owned assets, build them in Gitea Actions, and prove package install plus explicit activation on the target display.
Label the evidence separately at each step: source committed, package built, package installed, candidate activated, and real tool round rendered. Do not call the last item complete from a clean QML startup alone.
Candidate tabs (Casey)
- Agent Health — context used vs set, subconscious alive/failed with the
actual error, memory git state (dirty/ahead/behind), energy balance, todos,
itinerary. Every item already exists as live state and is drawn nowhere.
Note: used-context has no truthful source today— resolved 2026-08-12.7530d6dmadeContentBlock::countable_text()the exhaustive authority,f266136named the wire fields (tokens_used/context_limitwere being conflated — the pill was printing the limit as the usage), and r467 is installed and running. Patch 0007 landed the pill. The breakdown endpointGET /v1/conversations/:id/tokensalready returns per-role figures with zero consumers — that is the click-through. Group the breakdown by block kind, not role: measured on a real thread it is tool_result 43% / tool_use 31% / text 18% / reasoning 7%, and per-role filing puts 74% of that under "assistant", which reads as the agent being chatty and points at the wrong lever. - Conversations list / instantiation selector (Casey, 2026-08-12) — moves
into the pane.
Souveraine.newConversation()already exists and is what/newcalls;GET /v1/conversationsis the listing surface, sorted byupdated_at. Note there is no conversation search anywhere in the codebase — 148 conversations for this agent alone — so a flat list will stop scaling early. QMD (tobi/qmd, AUR) indexes markdown, which would want a markdown projection with the JSONL staying authoritative. - Reflection / subconscious — her thread, her inbox, her failures.
- Anime/persona — moves off the top level; it is a persona surface, not a system surface.
- Subagents — deferred: the standing no-subagent rule makes this a window onto an empty room. Decide the rule before building the viewer.
Also wanted, same lane
- Paste affordance. The TUI flagged pasted content and wrapped it for the agent. The GUI does not, which is why a handoff document addressed to another instance was read as a message to me on 2026-08-10.
- Image intake at the UI end. The server side landed 2026-08-11
(souveraine
cfad688): the message API now accepts"content"as an ordered array oftext/imageparts, gates on the model's vision capability at the door, and refuses engine-owned block kinds.Ai.qml::attachFile()still refuses attachments and its comment now cites a limitation that no longer exists. Owner of the inbound contract isdocs/substrate/tasks/multimodal-and-input-metadata.md; the picker, the chip, the base64 read and the rendering are this task's. - Resume offer (patch 0001) has no continue affordance until this lane lands; default-fresh works, one-tap-continue does not exist.
Discipline (non-negotiable, learned the hard way)
Do not edit the composed config. Souveraine-owned manifest files there are
symlinks into the repo, while ii-base is a copied vendor snapshot; both are
wrong places to develop. Stage changes in a detached worktree and keep patch
queue order explicit. The 2026-08-11 deployment was a Casey-authorised bridge
deployment, not a new normal.
There is no laptop build claim here. Gitea Actions owns reproducible builds.
For a deliberate compose/restart, inspect the exact QuickShell instance, run
deploy.sh, restart only that instance, inspect its fresh log for ERROR, and
require Configuration Loaded. Never use an empty search result as proof that
the source is absent; resolve symlinks and use grep -R where the composed
tree is involved.