Dated proposal · #17 · 2026-09-04 · hand-written and frozen, not a description of shipped behavior

Buddy Chat

The chat surface a Summon opens, for #17. The question it exists to answer: does a log-style chat this plain — the window ADR-0010 allows and no more — carry a real Harness turn without becoming a workbench, and does it still look like the same app as the sprite beside it? Drive the three states below and judge the copy; every string here is up for argument.

The surface belongs to the Character Instance that was Summoned, and ai-buddy draws it: the Harness runs headless in ACP mode and renders nothing. So the window carries four kinds of line and no fifth — the user's turns, the agent's text as it streams, tool calls as one-liners, and the forwarded session/request_permission prompt with the Harness's own choices. Each turn's WHO line carries a local HH:mm (#445), compact enough for the 420-point window; hover is not drawn here, the shipped surface puts the full datetime on title.

Deliberately not drawn: tool-call detail, diffs, file trees, plans, a skill browser, a settings pane. Those are the Harness's own surfaces, and reaching for one here is how this window turns into a second, worse copy of the TUI ADR-0010 refused to embed. What ai-buddy has to remember of them belongs in the Action Log, not on this scrollback — including what a sensing call returned, where the one-liner says only that it ran.

bmoBMO
BMO
spatial: fidget · idle harness: attached · idle

Both traces run at once on purpose. The left one is the Spatial Layer, which is model-free and never waits for anything; the right one is the Harness session. Watch them during the slow turn: the buddy is playing Behaviors the whole way through, because nothing in the frame loop is allowed to block on a turn.

Sensing, and whose rules it runs under

The slow turn reaches the desktop before it answers, because a chat message is the On-Demand tier's trigger: ADR-0005 defines that tier as a Capture in direct response to a Poke, a call, or a chat message, and this window is where the message is typed. What it calls are the two sensing tools ai-buddy owns. describe_screen and list_windows return window metadata rather than pixels in v1, both pass ai-buddy's denylist so a password field or an excluded application never enters a result, every Capture passes the Local Gate before it may reach the Director, and the capability behind them is consented once in settings rather than per capture.

The Harness's own computer use is not that. Claude Code performs it natively, and ADR-0003 is why ai-buddy posts no input events at all: desktop control belongs to the Harness. Its screenshots and its clicks run under its own permissions and its own dialogs, through neither our denylist nor our Local Gate — we own consent for sensing only. Both kinds reach this log as the same shape of one-liner, which is why the difference has to be legible somewhere it lasts: both belong in the Action Log (#16).

And a Capture can arrive with nobody having asked. An Ambient-tier Capture on a proactive wake has no chat message in front of it and may have no window open to show it in — the honest limit of this surface, because a scrollback nobody is looking at cannot be a privacy indicator. ADR-0005 gives that job to the Character: eyes open, turned toward the window, visibly unable to look while asleep. The trace above says the Character would be looking where the real thing is animation this page has no frames for, and inventing a looking pose would be the page claiming what it cannot show. A turn that has read the screen can also answer spatially — a Behavior that names a place, walk to the window you are working in, perch on its edge — which is an answer through the Character with sensing behind it. Nothing here draws that either: the Behaviors a Character declares name motion, not places, and #144 item 2 and #154 own the change.

The three states

A turn in flight, slowly
Seven seconds, streamed. Two sensing one-liners, a permission prompt that waits for you, then the reply arrives a few characters at a time. The window says it is working; it does not spin.
Judges the buddy keeps moving and reacting throughout a Harness turn — the criterion the issue's manual test names, against a fake Harness with an artificially slow turn.
An answer through the Character
One turn, three places. The Harness calls speak and plays a Behavior, both of which show as one-liners; the line lands in the Speech bubble over the sprite and the Character plays report. The chat keeps the long form.
Judges answers can arrive through the character as Speech and Behaviors, not only as chat text. The bubble is #119's, held for reading time (900 ms + 55 ms per character, clamped to 2–8 s).
No Harness attached
Copy, not a failure. What to attach, what attaching costs, and what the buddy still does for anyone who declines. The composer is disabled rather than hidden, so the window reads as waiting instead of broken.
Judges with no Harness attached, the surface explains how to connect one. Most people open this state first, so its copy is the part worth arguing over. In the app that button belongs in Settings; here it plays the slow turn.

Two of #17's criteria are not settled here. Summon opens the chat surface for the Instance that was Summoned needs a Summon, and there is none on this page — the window is simply open. Chat and idle Behavior proposals share one Harness session cannot be shown by a static page at all: the Behaviors beside the window come from a local weighted picker, and the Spatial Layer is model-free either way. Both are the Shell round's to prove.

Two decisions, one of them a reversal

The Harness here is a script and a timer — there was no ACP client in the tree on the date above. Timings are the proposal, not the ceiling: the seven-second turn, the streaming rate and the bubble's reading time are all knobs. Ported from src/main.css: the bubble, its tail, and the ink-on-white the window borrows.