Files
paseo/docs/connected-hosts.md
T
Mohamed Boudra 91dca5702b Connect saved hosts and delegate agents across daemons (#7370)
* feat(hosts): connect to saved hosts through a daemon

* fix(hosts): validate safe targets and verify encrypted relay routes

* test(hosts): include relay coverage in existing CI contracts

* fix(cli): preserve validated addresses in daemon command hints

* fix(hosts): share daemon links across isolated routed sessions

Multiplex logical sessions over the monitored daemon connection and wrap original binary bytes in routing headers. Admit channels under their authenticated parent and current grants; preserve scoped delivery and physical send completion.

* fix(hosts): bound encrypted overload and initial acquisition

Check encrypted wire capacity before the async channel converts a resource limit into a physical connection error. Bound initial routed acquisition with the existing client timeout, then enable reconnect after admission.

* fix(hosts): keep compact row actions clear of host labels

Give compact connected-host rows separate text and action widths so long names and producer labels cannot compete with primary controls.

* refactor(hosts): remove management settings UI from phase one

Keep connected-host management in the CLI and shared SDK while preserving app saved connections and plugin access.

* test(hosts): control physical write backpressure across platforms

Hold real TCP writes until assertions finish instead of assuming a paused receiver exhausts every platform receive buffer. Preserve physical send settlement, byte identity, capacity and sibling checks.

* refactor(hosts): use the ordinary client contract over channel transports

Keep queuing, permission codes and reconnection independent of transport selection. Track submitted request outcomes generically while retaining connection error codes and structured causes.

* test(e2e): keep transient developer toasts from stalling checks

Concurrent locator handlers can wait for a dismiss button another handler removed. Dismiss existing toast DOM at document lifetime, including its open shadow root, without intercepting app actions.

* Create, prompt, and monitor agents across connected hosts (#7507)

* feat(hosts): delegate agents across connected hosts

Resolve MCP host operations through in-process commands for self and the existing routed client transport for other destinations. Keep ordinary parent labels, app sessions and idle finish notifications.

* test(app): normalize subagent fixtures through snapshots

* docs(protocol): clarify same-host parent labels

* fix(hosts): resolve subagents through destination sessions

A child address identifies its destination regardless of the connection used. Reuse the existing host candidate selection for discovered routes and direct connections, and keep navigation and row actions independent of the parent transport.

* fix(hosts): keep routed destinations distinct during discovery

A routed candidate names only the origin daemon. Match it within the named destination profile so two destinations discovered through the same origin retain separate identities and connections, including existing saved routes.

* feat(hosts): prompt and query agents on selected hosts

Extend the existing MCP host operation boundary with prompt, status, activity and list operations. Local calls retain their handlers and notification owner; selected destinations use ordinary client RPCs and the existing idle finish wait.

* test(e2e): preserve toast coverage with CSS hiding

* docs(hosts): translate connected host references
2026-10-21 16:10:47 +07:00

90 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Connected hosts
A saved host belongs to the daemon that stores it. The app's saved connections remain a separate list.
Connect A to B through `paseo --host A host` or `paseo.hosts`. Consumers select B by its learned `serverId`; management uses the stable
`registrationId` created before B is reachable.
## Ownership and authority
Credentials live in A's `hosts.json`, written atomically with mode `0600` on POSIX systems. Discovery returns a safe
target description. Replacement inputs never come back through a read API. Keep this file private
when collecting diagnostics or backing up daemon state.
The authenticated producer owns its source key. Repeating an unchanged add returns the saved entry,
including a user's disabled state. Changed input requires an explicit update. Display labels and
access can be changed by users with `tunnel.manage`; connection and metadata changes require the
producer or `access.manage`. Removing a producer-managed entry allows that producer to add it again.
Disabling retains the opt-out.
Each enabled registration owns one physical daemon link, shared by status monitoring and every
routed API scope. Each scope owns an isolated virtual session on B. A derives its identity from the
admitted origin session, producer, and logical SDK scope. B admits virtual sessions under the actual
physical link principal, then intersects forwarded caller grants with that link's current authority.
Changing or resuming a parent's grants closes its existing channels; reconnect re-admits demand with
current authority. A routed API cannot select a second hop, and saving a host grants no reverse access.
A producer that registers a host through a routed API is qualified on B as
`route:<B principal>:<A serverId>:<A producer>`, with each component percent-encoded. This namespace belongs to the authenticated link
principal. An ordinary direct B consumer does not become that producer by supplying a source key.
The optional `server_info.sourceId` reports this producer identity separately from the authenticated admission principal.
Trusted server plugins retain the authority of their runtime; they are not sandboxed by this feature.
## Delegating agents
Agent MCP discovers destination IDs with `list_hosts`. `create_workspace`, `list_workspaces`,
`create_agent`, `send_agent_prompt`, `get_agent_status`, `get_agent_activity`, and `list_agents`
accept `serverId`; omission selects the calling daemon. Remote creation requires a
destination workspace and explicit provider/model. Caller filesystem defaults apply only on the
calling daemon. Prompting a remote child acknowledges acceptance and uses the existing idle wait for
finish notifications. Remote agent lists have no caller-directory default; pass `cwd` to filter them.
The parent’s Subagents track shows qualified children across connected hosts. Opening a child uses
the ordinary conversation on the destination's session. The runtime selects among that host's
connection candidates, including direct connections and routes discovered through another daemon.
Adding a direct connection to a discovered host adds another candidate. If no candidate connects,
the destination workspace keeps its existing disconnected or reconnecting behavior.
Finish notifications use the existing idle wait, including a pending initial run or an already idle
child. They name the server and agent. A daemon restart or route loss ends an outstanding notification
wait; inspect or wait for the child again rather than resending its prompt. Concurrent prompts gain
no additional turn attribution or delivery guarantee.
## Connections and recovery
The shared Node connector owns the target grammar and each connection attempt, including a fresh
SSH process. SSH reaches an already running daemon. The shared client connection lifecycle owns
physical reconnect and keepalive; the registry retains configuration, identity acceptance, and recovery
status. Unreachable registrations remain saved. Identity is pinned when discovered. Self and duplicate
registrations remain inspectable; a changed destination requires explicit identity replacement.
Both daemons advertise `connectedHosts`; the shared link additionally requires `hostMultiplexing`.
A completes capability and pinned-identity checks on each physical recovery before admitting channels. There is no local fallback. An older daemon continues to serve existing local
operations; requesting this feature gives update guidance.
Disposing a routed API releases its observations and session. It does not remove the saved host or
affect sibling API scopes. Disposing the origin cascades to its derived scopes. Observations restore
through the usual owned-subscription contract. The ordinary client queues new requests while connecting
within its existing deadline and rejects requests while disconnected. Connection loss clears old
waiters and queued sends. Submitted requests with no response fail with `DaemonConnectionError`
(`DAEMON_CONNECTION_LOST`), optional `outcome: "unknown"` and known `serverId`. Inspect the destination
before retrying a mutation.
Ordinary non-recoverable mutations are not automatically replayed. Existing acknowledged/idempotent
creation recovery retains its request identity and resumes through the ordinary creation lifecycle. Multiplexing changes only the transport: ordinary permission
RPC codes pass through unchanged, and connection/recovery behavior matches direct clients.
Text session envelopes carry a channel identifier. Binary routing adds an opcode, a bounded UTF-8
channel identifier, and the original terminal or file-transfer bytes. It never converts those bytes to
base64 JSON. The existing direct binary opcodes and 100 MiB message limit remain unchanged; routed
payloads reserve space for the routing header inside that limit. Relay encryption retains its own
existing frame limit.
Host links cap saved registrations, simultaneous channels, handshake lifetime, frames, physical
output, and aggregate queued bytes. Capacity checks use the actual escaped outer JSON or binary
frame size. Awaitable output completes when the physical send completes. An overflowing channel
closes without closing siblings or the saved link. Routed sessions release resources on disconnect;
subscription demand is rebuilt by its owning client on reconnect.
See the [SDK reference](../public-docs/sdk/reference.md#clienthosts),
[CLI reference](../public-docs/cli.md#connected-hosts), and
[plugin reference](../public-docs/plugins/reference.md#connected-hosts).