* fix(server): let Hub title created workspaces Agent creation can provision a workspace under hub.execute, but a separate title request required workspace.manage. Permit the same execution authority for title changes and their responses, and lock the Hub request contract with socket and permission tests. * test(server): keep Hub title repro free of conditional flow
8.1 KiB
Paseo Hub relationship
Paseo Hub is an explicit opt-in connection from one Paseo daemon to one Hub. Running a daemon does
not register it with a Hub. The relationship begins only when a user runs
paseo hub connect [url] from the daemon machine with an explicit API key or matching stored CLI login.
The human CLI login and daemon relationship are separate identities. paseo hub login [url] stores a durable organization-scoped CLI credential keyed by normalized Hub origin under PASEO_HOME. Interactive login optionally connects the local daemon, then points to the Hub UI for trigger configuration; it does not scaffold or deploy configuration. paseo hub init remains the explicit triggers-as-code scaffold: it writes one self-contained organization trigger under .paseo/triggers/, validates it through the trigger API, and optionally installs it. paseo hub deploy validates and installs every trigger in that directory; passing --project keeps deploying the legacy project bundle instead. paseo hub export [directory] writes the active organization's current triggers in the same layout, using the active login unless another Hub or API key is selected. Origin resolution uses explicit command input, PASEO_HUB_URL, active login, then https://hub.paseo.sh. Connect uses exact-origin authority to request a one-time enrollment token, then passes only that token to the daemon. The daemon generates and persists its own relationship credential.
Connection and authority
The daemon enrolls over HTTP(S), then opens and maintains a direct outbound WebSocket to the Hub. The Hub never discovers or acquires the daemon through Paseo's relay. The relay remains an optional encrypted path for normal Paseo clients and has no role in Hub enrollment, authentication, dispatch, or reconnects.
The daemon persists a relationship ID and private connection credential before enrollment. The relationship is independent of its current transport, so a future transport can replace the direct WebSocket without pairing again. The current foundation supports one Hub relationship per daemon.
Normal authenticated daemon sessions may manage the daemon's Hub relationship and permissions.
Hub connections have no daemon permissions by default. Connecting gives Hub machine identity and
presence but no execution authority. The hub.execute permission lets workflows triggered from
GitHub, Slack, Discord, Linear, and other integrations create workspaces and run agents. Grant it
during interactive login or later with paseo hub permissions grant hub.execute. Relationships
created before this split migrate their legacy execution scope to hub.execute. Hub sessions cannot
manage their own relationship or permissions.
Session grants and agent operations
Hub uses the same authenticated, resumable Session protocol as other clients. Its persisted
hub.execute permission authorizes ordinary agent creation, workspace titling, messaging,
cancellation, archival, agent/workspace observation, timeline subscriptions, and workspace recovery. This authority is
daemon-wide; it is not limited to agents created by that Hub. Daemon configuration, terminals,
browser control, and permission management still require their own permissions. See
permissions.md.
Creating an agent may create a directory workspace. The same hub.execute session can title that
workspace through workspace.title.set.request without workspace.manage.
Clients using this contract check server_info.features.hubAgentRpc and
server_info.features.agentRequestReceipts once. An older host must be upgraded; do not silently
fall back to creating a fresh agent when continuation was requested.
Hub owns conversation keys, trigger policy, execution records, and the mapping to workspace/agent IDs. None of these routing concepts are part of the generic daemon RPCs. Creation accepts ordinary provider configuration, exact MCP tool policy, environment, and workspace/worktree selection. Private session configuration is persisted for recovery and is not exposed in agent snapshots. Provider controls remain provider-native; see providers.md.
create_agent_request.idempotencyKey identifies one creation operation. With a key, omit
initialPrompt, persist the returned agent/workspace identity, then deliver the prompt using
send_agent_message_request with a stable messageId. Request IDs correlate individual attempts;
creation keys and message IDs identify the operation across attempts. A creation key is daemon-wide;
a message ID is scoped to its agent. Reusing either with different arguments is a conflict.
workspace.create.request.idempotencyKey provides the same creation guarantee for directory and
worktree workspaces; check server_info.features.workspaceRequestReceipts before using it.
Workspace creation keys and agent creation keys have separate namespaces. The app keeps these keys
for the lifetime of a draft and reuses the first message ID after failures.
The daemon journals the assigned agent ID before creation. Concurrent retries share one operation,
and a retry after a lost acknowledgement or restart returns the durable agent without creating a
workspace or starting a turn. Deleting that agent does not make its old creation key reusable.
Confirmed message delivery is also journaled, so retrying its message ID does not submit it again.
An unfinished delivery after restart reports agent_request_outcome_unknown: a provider can have
accepted a prompt before the daemon recorded success, so automatic resubmission could duplicate
work. Inspect the agent before choosing a new message ID. Receipts contain identity and request
hashes, never prompts, environment values, or credentials.
Before messaging an archived workspace, call workspace.recovery.inspect.request, then
workspace.recovery.restore.request and await success. The native message handler unarchives the
agent and loads its persisted provider session. activeTurnBehavior: "steer" uses the provider's
native steering behavior, including Paseo's existing behavior when that provider cannot steer.
Execution completion and arrival-specific output authority remain Hub responsibilities.
The older hub.execution.* RPCs remain accepted for existing clients. Their execution ownership
and create deduplication behavior are unchanged; new continuation work uses the ordinary RPCs.
Disconnect and revocation
Normal socket loss reconnects the active relationship with bounded exponential backoff and jitter. Daemon restart loads the same relationship and credential and reconnects without another enrollment ceremony.
Hub authentication rejection or close code 4403 permanently revokes the local relationship. The
daemon deletes its credential, stops reconnecting, and retains only the relationship ID, Hub origin,
scopes, and a sanitized reason for status reporting.
paseo hub disconnect disables socket reconnect and execution authority before making one bounded
remote revocation request. The daemon then removes the local relationship whether the request
succeeds or fails. A failed request returns a warning that server-side revocation may remain pending.
--force skips the remote request. Legacy persisted disconnecting records are removed on startup;
the daemon does not retry revocation in the background.
paseo hub logout removes only the active human CLI credential and preserves credentials for other origins. Interactive logout inspects and optionally disconnects a same-origin daemon before deleting the login; a failed requested disconnect preserves the login. JSON and noninteractive logout never prompt or disconnect implicitly.
Cross-repository compatibility
The consumer implementation lives in Paseo Cloud. Cloud owns its copy of the Hub wire schemas and
has no Paseo runtime or build dependency. Cross-repository end-to-end verification separately builds
a Paseo source checkout and exercises the real daemon, CLI, direct WebSocket, Cloud service, and
Postgres. That compatibility fixture is not a package dependency or fallback implementation.
Its hub-e2e ACP provider accepts only exact tool names on the injected hub MCP server. Other
custom ACP providers remain unsupported for unattended preapproval.