Remote TUI Guide
Remote TUI lets one machine run the XiaoO gateway daemon while another machine runs the terminal UI.
- Machine A runs
xiaoo-daemonand owns the runtime, LLM provider, tools, hooks, workspace, and operation backend. - Machine B runs
xiaooand connects to Machine A with/remote. - Both machines use the same codebase and binaries; only the startup mode is different.
1. Architecture
Machine B Machine A
xiaoo xiaoo-daemon
--------- ----------------
TUI input/rendering HTTP/SSE Gateway runtime APIs
/remote commands -----------> Agent loop
Interaction prompt <----------> Tools / hooks / workspace
Local TUI remains the default. Remote mode is opt-in:
Local: TUI opens runtimes and runs the agent loop in the local process.Remote: TUI sends turns to the daemon and renders the daemon's SSE events.
In remote mode, all tool execution happens on Machine A. The workspace shown in the TUI status bar is marked as remote to avoid confusing it with Machine B's local directory.
2. Start Machine A
Start the daemon on Machine A:
xiaoo-daemon \
--host 0.0.0.0 \
--port 18080 \
--config ~/.config/xiaoo/config.toml
Recommended daemon auth configuration:
[http]
bearer_token_env = "XIAOO_HTTP_BEARER_TOKEN"
Then export the token before starting the daemon:
export XIAOO_HTTP_BEARER_TOKEN="change-me"
xiaoo-daemon --host 0.0.0.0 --port 18080
Health check:
curl http://A:18080/api/v1/health
If bearer auth is configured, protected session/chat routes require:
-H "Authorization: Bearer $XIAOO_HTTP_BEARER_TOKEN"
3. Start Machine B
Start the TUI normally:
xiaoo
Connect to Machine A:
/remote http://A:18080
If Machine A uses bearer auth, configure Machine B's TUI config:
[tui.remote]
url = "http://A:18080"
bearer_token_env = "XIAOO_REMOTE_TOKEN"
auto_connect = false
Then export the same token value on Machine B:
export XIAOO_REMOTE_TOKEN="change-me"
xiaoo
When auto_connect = true, TUI enters remote backend mode on startup using the configured URL. When auto_connect = false, the config only supplies the bearer token env var and default remote settings; use /remote <url> manually.
4. TUI Commands
| Command | Description |
|---|---|
/remote <base_url> |
Connect to a remote gateway daemon, for example /remote http://A:18080 |
/remote status |
Show current backend, remote URL, session-open state, and health result |
/remote off |
Close the remote session and switch back to local backend |
/new |
Start a new TUI session; in remote mode this closes the old remote session first |
After /remote <base_url> succeeds, new turns go through Machine A's daemon. The status bar shows Remote: <base_url>.
5. Remote Session And Runtime API
Remote TUI uses the daemon's runtime control APIs. The same protected route group also contains checkpoint APIs for programmatic clients that need branching runtime state.
| Endpoint | Description |
|---|---|
POST /api/v1/runtimes/open |
Open or resume a runtime using RuntimeOpenRequest |
POST /api/v1/runtimes/input |
Submit one user input and stream SSE events |
POST /api/v1/runtimes/interaction |
Send a user interaction response back to the daemon |
POST /api/v1/runtimes/cancel |
Request cancellation of the current turn |
POST /api/v1/runtimes/close |
Close the runtime, remove its record, and fire lifecycle hooks |
POST /api/v1/runtimes/checkpoint |
Capture an idle runtime as a checkpoint |
POST /api/v1/runtimes/checkpoint/delete-snapshot |
Delete the provider snapshot referenced by a checkpoint |
POST /api/v1/runtimes/checkout |
Create a new runtime from a checkpoint |
POST /api/v1/runtimes/pause |
Snapshot an idle runtime and release its live backend |
POST /api/v1/runtimes/resume |
Restore a paused runtime with the same runtime id |
POST /api/v1/runtimes/exec |
Run a shell command inside the runtime's backend |
POST /api/v1/runtimes/read-file |
Read a file from the runtime's backend |
POST /api/v1/runtimes/write-file |
Write a file inside the runtime's backend |
Remote TUI only consumes the open / input / interaction / cancel /
close endpoints directly; checkpoint, checkout, pause, resume,
exec, read-file, and write-file are programmatic control-plane endpoints
exposed on the same protected route group for other clients. See
runtime_checkpoint.md for the checkpoint/pause/resume
semantics and apps/serverside/src/httpserver/router.rs for the authoritative
route list.
Runtime control payloads use runtime_id and checkpoint_id as their public
vocabulary.
SSE event types:
| Event | Description |
|---|---|
turn_start |
Agent loop turn started; carries agent_id (root or subagent) |
text_delta |
Assistant text update; includes both incremental delta and cumulative snapshot; agent_id disambiguates root vs. subagent lanes |
thinking_delta |
Assistant reasoning text update (mirrors text_delta semantics) |
tool_result |
Tool execution result summary; carries agent_id, call_id, tool_name, output_preview, is_error, and args_preview |
tool_call |
Tool lifecycle transition (running / completed / failed / denied). Forwarded by the daemon so the remote TUI can drive the same tool-card state machine as local mode (running spinner, terminal state). agent_id routes the update to the root message list or a subagent lane |
tool_file_change |
Per-call file change delta precomputed by the daemon so the TUI's session diff panel mirrors the local computation |
plan_update |
Plan snapshot parsed by the daemon from the todo_write tool's args |
subagent_spawn |
Subagent lane metadata parsed by the daemon from the spawn_subagent tool's args + output; lets the TUI create the subagent lane without re-parsing the daemon-only args_preview |
loop_end |
Per-agent loop-end marker. The daemon emits one per agent_id (root or subagent) so the TUI can clear is_running on the matching subagent lane as its loop terminates, matching local-mode ChannelLoopEventSink::on_loop_end semantics. Carries the per-agent LoopEndSummary fields (turn_count, total_tokens, stop_reason) so the TUI can render per-agent token usage / stop reason with parity to local mode; older daemons that omit them default to zero / empty via #[serde(default)] |
interaction_requested |
Daemon asks the TUI to show an interaction prompt |
done |
Turn completed; includes token usage and runtime messages |
error |
Turn failed |
cancelled |
Cancellation acknowledgement |
Backward / forward compatibility. The TUI's SSE parser deserializes
each frame into the RemoteSseEvent enum, which carries an
#[serde(other)] Unknown catch-all variant. Unknown event types (emitted
by a future daemon) are mapped to Unknown, logged at debug level, and
skipped (not surfaced as a stream error), so a TUI built against this
catalogue keeps working when a future daemon emits additional events — no
hand-maintained string whitelist is needed, and adding a new variant to
RemoteSseEvent automatically makes it a known type. New fields on
existing events are added with #[serde(default)] so older daemons that
omit them still parse on a newer TUI, and older TUIs that don't know about
a new field silently drop it (serde's default is to allow unknown fields). Daemon authors adding new
SSE events or fields should mirror the snake_case naming of the existing
catalogue and document the additions in this section.
6. Operational Notes
- Machine A's config controls the LLM provider, model, workspace, tools, hooks, LSP, and operation backend.
- Machine B's local provider/model config is still used for normal local mode and for TUI bootstrap, but remote turns execute with Machine A's daemon config.
- Use bearer auth for any daemon bound to a non-loopback interface.
- For untrusted networks, prefer an SSH tunnel or TLS-terminating reverse proxy in front of the daemon.
- Remote runtime state is kept in the daemon's in-memory control-plane store. Restarting Machine A's daemon loses active remote runtimes in the current implementation.
- Subagent support. The daemon binds the same
SubagentControlimplementation (CoreBackedSessionService) as the local TUI, sospawn_subagent/join_subagenttools work in remote mode out of the box. Subagent lane lifecycle (turn_start→tool_call→loop_end) and tool-card running state are forwarded via SSE so the TUI renders subagent lanes with parity to local mode. Configure[subagent.<id>]role presets on Machine A; roleprompt/tools/max_turnsare applied at spawn time.
7. Current Limitations
/cancelis wired through the HTTP/TUI path, but hard cancellation depends on the gateway/core exposing the active loop cancellation token through the session supervisor.- Remote mode does not sync files from Machine A to Machine B. Tool results and file-change summaries are streamed, but filesystem operations happen only on Machine A.
- Remote TUI is not a separate lightweight client package; it is the same
xiaoobinary running with a remote backend.
8. Quick Checklist
- Machine A has daemon config and provider credentials.
- Machine A starts
xiaoo-daemon --host 0.0.0.0 --port 18080. - Machine B can reach
http://A:18080/api/v1/health. - If auth is enabled, Machine B exports
XIAOO_REMOTE_TOKEN. - Machine B starts
xiaoo. - In TUI, run
/remote http://A:18080. - Send a message and confirm the status bar shows
Remote: http://A:18080.