Desktop Inspector Chat
The desktop app’s machine inspector ships with a per-machine chat panel. Open a machine, switch to the chat tab, and you are talking directly to the agent on that remote machine — not to your local agent and not to a proxy. Replies stream from the remote agent’s mouth to your screen with no paraphrasing in between. We call this pattern Path A.
Why a direct pipe
The naive design is “local agent, remote tools”: your laptop’s LLM runs
the chat loop, calls ask_agent for each remote question, and surfaces
the reply. That works (it is the basis of CLI machine
chat), but it has one quality problem — the local
LLM tends to paraphrase the remote agent’s reply, smoothing it into the
local model’s voice and dropping technical detail.
For a debugging-focused inspector tab, that is the wrong default. We
want the operator to see exactly what prod-api-1’s agent thinks,
formatted by prod-api-1’s prompt and tools. Path A bypasses the
local LLM entirely and pipes the remote stream directly to the UI.
The implementation lives in
internal/desktop/services/chat/service_send_remote.go and
internal/chat/CLAUDE.md §Per-machine chat.
Session ID convention
Path A is keyed off the session ID format. The frontend mints session IDs in the shape:
machine_<uuid>_<hex>For example: machine_8f23a4b0-d5c7-4f02-9c11-7b1e8a36f1d2_a3f9.
The desktop chat service decodes the prefix, extracts the target
machine UUID, and threads it as chat.Optional.TargetMachineID into
the session.
When runSession sees a non-empty TargetMachineID, it skips the
local LLM and dispatches directly to remoteagent.AskStream.
How a turn flows
- User types a message in the inspector chat panel.
- Frontend posts the message to the desktop chat service with the
machine_<uuid>_<hex>session ID. - Service decodes the target machine UUID, calls
chat.SendwithTargetMachineIDset. - Session router sees a non-empty target → bypasses local LLM →
calls
remoteagent.AskStream(see server-to-server for the funnel). - Remote agent runs the prompt under its own system prompt, tools, and permission rules.
- Stream comes back as
chat:chunkevents, each forwarded directly to the UI asTOKEN,TOOL_START,TOOL_END, orERRORevents. - Final text is persisted via
Session.RecordTurn()so the transcript survives a restart and can be resumed.
The local agent never gets a turn. Your laptop is doing transport-and-display, nothing else.
The remote agent’s prompt and tools shape the reply. If the inspector chat feels different per machine, that is by design — you are talking to that machine’s agent loop.
What the UI shows
The inspector chat panel renders the same event stream the agent loop emits:
- Token chunks — appended to the current reply bubble.
- Tool start / end — collapsible cards showing the remote agent’s tool call and its result.
- Errors — surfaced inline (the remote agent reported a failure).
- Cancellation — the user pressed stop; the remote run is told to cancel via context cancellation.
Because the stream is the remote agent’s actual output, you see what it did: which files it read, which commands it ran, which other machines it asked. No paraphrasing layer.
Permission gate fires on the remote
The receiver’s permissions.yaml decides what the remote agent may do
in service of your prompt. If you ask “delete /tmp/foo” in the
inspector chat for prod-api-1, that delete is gated by
prod-api-1’s rules, not your laptop’s.
The self-to-self exception applies: if you and the remote machine
share the same OAuth identity (verified via CallerHostname), the
gate is bypassed. This is what makes single-operator multi-machine
flow ergonomic. For team setups where you want hard gates everywhere,
run remote agents under a different identity. See
../concepts/permissions.
Self-to-self calls (same OAuth user) skip the permission gate by design. Pair-debug with a teammate watching the inspector chat — that teammate’s permission rules apply, not yours.
Restart resume
Session.RecordTurn() is the bridge to durability. Each completed
turn (user prompt + remote reply) is recorded so:
- Closing the inspector tab and reopening it restores the transcript.
- A desktop app restart preserves the conversation.
- Multiple desktop instances looking at the same machine see the same history.
The remote run itself is not persisted — only the transcript. If the remote agent was mid-stream when the desktop app closed, the run was cancelled and you start fresh next turn.
Streaming caveat (resolved)
There was a stretch of 2026-04 when daemons sent the entire reply as
one final envelope without intermediate TOKEN events. During that
window, the desktop direct pipe used the unary Ask so the user saw
the reply at-once instead of mid-stream. That gap is closed in
2026-04-26 and later daemons; the desktop now consumes
AskStream for true per-token streaming. See report 01 §9 for the
incident note.
If you connect to a daemon older than that fix, the inspector chat
still works — the UI just receives one big TOKEN at the end of the
run instead of a stream.
Path A vs Path B
| Path A (this page) | Path B (CLI —machine) | |
|---|---|---|
| Surface | Desktop inspector tab. | cmdop chat --machine X in a terminal. |
| LLM running the loop | Remote agent. | Local agent. |
| Tool calls | Remote agent’s full tool surface. | Local tools, plus ask_agent to dispatch remotely. |
| Output | Direct stream from remote. | Local LLM’s synthesis. |
| Best for | Debugging, inspecting, understanding what the remote thinks. | Multi-machine prompts where you want the local LLM to coordinate. |
| Permission gate | On the remote. | On the local and on the remote (per call). |
Both paths are first-class. Most operators end up using both — Path A for “look at this machine and explain” and Path B for “across these machines, find which one is degraded.”
Configuring the inspector chat
The inspector chat respects the same workspace and credential model as every other Connect surface:
- The active workspace decides which machines have an inspector tab.
- The workspace’s API key (or OAuth token) authenticates the
underlying
remoteagent.AskStream. - Per-machine passwords still apply — the desktop prompts inline if the remote requires one.
There is no separate config for the inspector. Open a machine, open chat, type.
Related
ask_agent_stream is the funnel underneath Path A.
Path B — local LLM with remote tools.
Streaming auth, machine passwords, session token cache.
The conceptual model behind both paths.