# Delegated seat contracts

## Delegated seats outside the manager's filesystem

The [portable lifecycle bootstrap](https://github.com/Cotal-AI/Cotal/blob/main/docs/design/portable-lifecycle-bootstrap.md)
design and SPEC §13.17 define how a managed agent that `enrollManagedAgent` already enrolled starts
in a child that cannot see the manager's filesystem. The ordinary `spawn` path writes the token and
sentinel under the manager's workspace root and hands the runtime a launch whose bearer command and
material file are paths on that filesystem, so such a child needs this path instead.


### Runtime handoff

The delegation boundary is one optional runtime method. A runtime that implements
`Runtime.spawnDelegated(launch, handoff)` receives two values and no paths: a `DelegatedSeatLaunch`
(connector name, persona text, `managerInstanceId`, model and the other launch choices) and a `ManagedLifecycleHandoff`
(space, owner, actor, the host-chosen `lifecycleUid`, broker and IdP pins, the pinned exchange base,
the sentinel, the channel lists, and the raw actor token). The manager enrolls once, as it does
today, and builds the handoff from what it holds. It never sends the token to the host, never copies
a file from its workspace or secret store, and never builds a local launch for that seat. No signer,
issuer or callout record, loopback capability, provisioner or manager credential, control token, or
manager path crosses. A spawn choice that only the manager's host can honour is refused before
enrollment: `--resume`, a manifest agent's `continuity: exact`, `--cwd`, and any shared MCP server,
whether from `--share-tools` or the config default (`--share-tools none` passes).

A delegated manager also refuses a caller-supplied persona `config` override in `resolve-agent`,
`spawn` and direct `Manager.startAgent` calls. The shared persona resolver refuses before checking
or reading that path, with `failed-precondition` on the wire and one fixed execution-mode message.
Pass a named catalog persona instead. This does not change locally executing managers. A resolved
manifest can still use its own validated, host-materialized persona resource. The wire schemas
accept no internal `resolved` field that could bypass the config refusal.

### Child bootstrap

The runtime creates one provider resource under `managedRuntimeKey(target)`, writes the handoff into
it as one 0600 file and the persona beside it, and runs the stock bootstrap there:
`cotal spawn --config <persona-file> --space <space> --name <actor> --expect-owner <owner> --expect-lifecycle-uid <uid>`
with `COTAL_MANAGED_HANDOFF_FILE` naming the file and `COTAL_MANAGER_INSTANCE` naming the launching
manager. `delegatedSeatCommand` builds that argv and `env`.
`cotal spawn` reads the file into memory, deletes it and drops the variable before it parses flags,
prints help or loads extensions, so every outcome, a refusal of its own flags included, leaves no
file. It refuses before any broker connection or exchange request when `COTAL_MANAGER_INSTANCE` is absent (`COTAL_MANAGED_HANDOFF_FILE requires COTAL_MANAGER_INSTANCE`; `DelegatedSeatLaunch.managerInstanceId` is required and a newer CLI child under an older manager or runtime refuses every delegated launch, so managers and the CLI must be upgraded together) or malformed, or when the
space, owner, actor, or lifecycle UID differ from the expected values, and then runs the
enrollment-redeem consumer: it registers the mesh in its own home, writes the token to its own 0600
file, and exchanges it through `agent-bearer --exchange-url` unchanged. It never enrolls, redeems, or
mints a token or UID.

### Provider closure guarantees

Readiness is still mesh presence. A create whose answer is lost leaves the handle running, so the
launch settles uncertain and stays held; the manager never retries it. A provider read that finds no
resource under the key is not an exit, because the create may still land. Every close by
`managedRuntimeKey` is fenced: it completes only once the create was answered or the provider refuses
any later create under the key. A provider that names its own resources may run the create as a
durable operation keyed by `managedRuntimeKey` and close through the identifier its authenticated
create response returned, kept where the host can read it without the manager. Only that response
binds an identifier to the key; one derived from the key or found by name or listing is never closed
or adopted, and while the response is unknown the launch stays held. Every stop, the reap of a child
whose parent exited and `Manager.stop({ withAgents: true })` included, runs `prepareAgentRetirement`
for the UID-exact target, then `stop()` on the handle `spawnDelegated` returned, then the terminal
barrier. `preparePreservation` refuses a cut that holds a delegated seat, and a `Manager.stop()` after
a refused cut retires the seat through the same steps. After the manager is gone
the host runs the same steps, makes the same fenced close by `managedRuntimeKey`, and finishes at
`MANAGED_RETIRE_PATH`. Supply `spawnDelegated` only from a runtime whose host can make that fenced
close without the manager. The enrollment redeem
(`COTAL_ENROLLMENT_FILE`) stays for lifecycles whose token the host generated itself; a host cannot
mint one for a manager-enrolled lifecycle because it holds only the digest.

## Delegated recovery contracts

Core exports `RemoteDelegatedRecoveryRequest`, `RemoteDelegatedRecoveryResult`,
`RemoteDelegatedRecoveryLaunchRequest`, their closed parsers, and
`parseDelegatedRecoveryDescriptor`. `Runtime.rebindDelegated` describes an optional same-child
rebind. These are contracts for SPEC §13.18, not an enabled recovery path. The current Manager
does not call the new method or reconstruct delegated rows. Its public `adoptRuntimeHandle`
returns a native handle without inserting a managed row.

A supporting host retains the descriptor in the original user-intent consuming CAS, binds each
claim to the original instance and current registration, and supplies complete fenced scans.
The manager needs its original persistent secret store and a runtime that can verify the same
child execution after process loss. It must retain held aliases before reconciling accepted goals.
Missing storage or unverified child identity keeps recovery held; neither permits another launch.
The request parsers validate data, not fresh host authority or durable transactions. Existing
hosts continue to refuse the opt-in launch extension rather than silently ignore it.

## Related contracts

### Refused launch choices

Host-dependent launch choices are refused before enrollment. See [the detailed contract](/docs/embedding-delegated-seats/#runtime-handoff).

### Retirement order

Delegated stop and retirement follow the provider closure guarantees. See [the detailed contract](/docs/embedding-delegated-seats/#provider-closure-guarantees).
