v0.63.0
Cotal 0.63.0 is out!
Grab it: npm i cotal-ai@0.63.0
Changes in this release
- Start an already-enrolled managed agent in a child outside the manager's filesystem (SPEC §13.17). A runtime may offer
Runtime.spawnDelegated(launch, handoff). A manager whose runtime does enrolls throughenrollManagedAgentonce, refuses--resume, a manifest agent'scontinuity: exact,--cwd, shared MCP servers and non-string launch options before enrolling, never builds a local launch for the seat, and hands the lifecycle off at most once as one closedcotal-managed-handoff/v1value carrying the issued owner, actor, host-chosen lifecycle UID, sentinel, pinned exchange base and the raw actor token, which the host never receives.delegatedSeatCommandbuilds the child'scotal spawn --expect-owner <owner> --expect-lifecycle-uid <uid>command withCOTAL_MANAGED_HANDOFF_FILEnaming the handoff file. Thecotalentry takes and deletes that file and drops the variable under any letter case before it parses flags, prints help or loads extensions, including when it refuses an older Node, and refuses spellings that name different files after deleting each of them. The spawn refuses a foreign space, owner, actor or lifecycle UID, an unknown field, a wrong kind and a file that is not a private regular file before any broker connection or exchange request, then runs the enrollment-redeem consumer without redeeming or minting, and exchanges throughagent-bearer --exchange-urlunchanged. No refusal on that path echoes a value from the handoff: the registration's server, exchange and enforcement checks, the local state this machine keeps for the space (its mesh record, user-auth state and agent secret files), target resolution, the policy refresh, the broker preflight, the agent auth preflight and the event-plane policy each refuse with a fixed sentence naming the field and the phase, whether a check fails or a filesystem, exchange or broker error is thrown under it, and an actor that is not a single token or a space that cannot name local state is refused as malformed before any plane. Readiness stays presence-observed, so a lost create acknowledgement settles uncertain and stays held. Every stop of a delegated seat, the reap of a child whose parent exited and a destructive manager shutdown included, runs prepare-retirement, then the handle's fenced close bymanagedRuntimeKey, then the terminal barrier. A preservation cut refuses a delegated seat at prepare time, and a manager stop after a refused cut retires it the same way. A provider resource is bound to that key only by the provider's authenticated answer to its create. The design record, the embedding guide, the CLI and configuration references and SPEC §13.17 describe the shipped shape. - A logged-in user can now run hosted workflows on a user-auth space. The auth callout issues an interactive user's
manager-callerview as an issuance (SPEC 13.15): it chooses the generation, records evidence whose one source is the user's actor-ledger row, and writes the accepted row under a token derived from the connection's inbox nonce, renewing that generation on a reconnect whose ceiling is unchanged.cotal runand the other manager calls read the generation back with the newissuedUserCallerand rideep.v1, socotal run startagainst a participant manager started withcotal superviseis admitted. The issuing host admits a run from a user caller only for the manager's registered owner, and a participant manager now forwards the served subject of every caller resume and answer, which the host checks against the run's owner and the caller's live issuance; a legacy-rail resume is refused. The issuing host subscribes to those resume and answer request subjects itself and issues for a forwarded one only when it observed that request, only once, and only for the run a resume's envelope names or the endpoint and amendment an answer's envelope names, and never for a request bound to another manager instance or epoch or declaring another class or pinning another contract than the manager registered for that command. Every answering operator issuance must carry that served subject, and an amendment's carriesanswers.amend: true, which the host accepts only for a pause settled with an accepted answer, socotal run amendworks on a participant manager. A user caller starts, resumes and answers runs only on the participant manager that user registered, and a static caller keeps its admission there. A managed seat's manager call keeps the legacy rail when the broker refuses its accepted-row read, which surfaces as a request error caused by the permission violation. Revoking or re-granting the actor makes its issuances dead at the next resolution. New exports:connectionAcceptedToken,actorLedgerSource,actorLedgerSourceBucket,parseActorLedgerSource,issuedUserCallerandisDerivedOwnerin core, andledgerActorSourceIsLiveandUserCallerIssuerin auth.manager-callerjoins the issuable profiles, and theissuerprofile gains the per-key read of the accepted store. SPEC §13.15 gains a User-auth issuance paragraph and §14.8 a User-auth runs paragraph, both insertions;docs/design/user-auth-run-start.mdrecords the path, anddocs/cli.md,docs/workflows.mdanddocs/run-a-mesh.mddescribe it.docs/run-a-mesh.mdalso says the stock auth service refuses managed-agent enrollment and retirement preparation for a host platform to intercept. - Drop the stream-wide
CONSUMER.DELETEgrants from the observer, admin and agent profiles. An observer could delete another principal's live presence, channel-registry and membership watches and the delivery daemon'sfanoutdurable, and an agent could delete a peer's presence and registry watches; those deletes are now refused by the broker. A client's refused delete of its own ephemeral watch, membership snapshot or history consumer, including the predecessor a watch, membership snapshot or history read deletes when it rebuilds after a stall or a delivery gap, is treated as handled and raises noerrorevent, and the broker removes that consumer five minutes after its last interest. - A presence record now says when its status was entered.
tsis the last heartbeat, so an activity that was true when an agent set it stayed on the roster long after it stopped describing anything, and nothing could tell it from a fresh one. Presence gains an optionalstatusSince: the epoch ms when the instance entered its current status and activity. A change to either moves it, while a heartbeat or a repeated report does not, and an offline record carries none.cotal_rosterprints its age beside the status, such asidle · unchanged for 40m. - A restarting manager on an authenticated mesh now verify-evicts its superseded credential family in one shared sweep per 256 holders instead of one holder at a time. The family keeps a ledger row for every credential an earlier incarnation was issued, and every boot adds fresh goal-writer and session-ledger holders, so the re-registration's eviction step grew with each restart: every holder cost a freshly minted credential, a new broker connection and its own delivery-admin request, and the progress cursor was rewritten after each one. After enough restarts the manager stayed off its endpoint rails for minutes while
cotal psgot no answer from it, and a sweep that outlived the executor window looped onmanager registration executor expired. Re-registration now sends the pending holders through theevictPrincipalsverb thatcotal reconcile-gateand the boot self-heal already use, records the verified ones after each request before it sends the next, and still leaves the gate frozen when any holder is not verified gone.cotal reconcile-gateand the boot self-heal now also record each request's verified holders before the next, so a family past 256 holders keeps the requests that already succeeded when a later one is refused or the executor window closes. The registration barrier'sevictseam in@cotal-ai/corenow takes the holder set and answers one verdict per holder, and the barrier'sevictMaxcaps how many holders one call carries. A remote manager's host still evicts one principal per maintenance call, so its barrier setsevictMaxto 1 and a refused host call keeps every holder the host verified before it. The 60-second wait for a delivery daemon that is still binding applies to each of those requests, and each request mints its own short-lived credential so the wait never outlives it. - Presence now carries
activitySince, the epoch ms when the current activity was set. A status change or a heartbeat does not move it, so an activity left behind while hooks flip the status every turn no longer reads as fresh beside astatusSincethat moved on the last flip.cotal_rosterprints its age after the activity, such as(set 9h ago), and dates only a finite stamp, so a record holdingnull, a string or an exponent literal such as1e400renders no age. - First-run
cotal setupnow shares your own Claude Code MCP servers with the sessions Cotal spawns. It copies the user-scope servers from your Claude Code config into the cotal config'sconnectors.claude.mcpServersand names them in its output, so a spawned session has the tools you know plus the cotal tools. Before this, a spawned session loaded only the cotal server and setup never said why. With none to copy it writes an empty list, and a cotal config that already declares that list keeps it. A server with anenvorheadersvalue that is anything but${VAR}references is left out and named, because the cotal config keeps secrets only as${VAR}references. So is an entry no session can start, such as one with a missing or emptycommandorurl, or one whosecommandis not a string. For a lighter seat, remove entries from the cotal config or spawn with--share-tools none. Connector setup providers gain an optionalmcpServersaction whoseseedinput the CLI binds to workspace's newseedConnectorServers, which writes under a lock so two setups run at once record one list. Core exportsreadCotalConfigFileand theConnectorShareSetupInputtype, and connector-core exports theENV_REFERENCEpattern it already used. - The
card-hostandep-rail-failuresmokes now wait for their broker to exit before removing its store. Both sent SIGTERM and removed the JetStream store on the next line, so the removal could walk a tree the broker was still writing during its graceful shutdown and fail the shard withENOTEMPTYafter every check had passed. Shipped behaviour is unchanged. - A
cotal uprefresh of a running mesh no longer reverts registry changes another command makes while it runs. The refresh rebuilt the whole record from a copy it read before ensuring the control plane, so atlsRequired: truewritten in that window went back tofalse, a recordedmaxFileStorewas erased, and the command still printed✓and exited 0. A refresh now writes only what it decided (server, root, mode, user-auth endpoints, an explicit--hostor--max-sessions) over the record as it stands at write time, so fields it does not set, including a registration'spolicy, are kept. If the record was removed during the refresh,upfails instead of writing it back. - A manager whose SecretStore differs from the delivery daemon's no longer holds the space's daemon-credential renewal lease. Its renewal pass already released the lease, but the lease heartbeat took it back a few seconds later and kept it until the next pass, six hours on. On the split topology with
cotal up --no-manageron the broker host and the only manager on another root, that manager never reminted and still blocked the renewal: a manager started on the daemon's own root lost the lease to it, andcotal doctor auth --fixon the broker host refused withheld by manager, sodelivery.credsandmembership-rw.credsexpired. The heartbeat now contends only while the manager's last store check found the daemon on its own store or found no daemon, sodoctor auth --fixon the broker host renews the daemon credentials again. - Stop a superseded manager from serving. If a manager stalled long enough for a second process of the same instance to take its liveness lease and register at a newer epoch, the first process logged that another pid held its key and kept serving. On an open mesh nothing evicted it, so both processes answered the instance's rail. A manager whose lease is held by another process now logs that the other process serves the instance, and exits. It does not deregister on the way out, because the registration is the successor's now. A lease that cannot be renewed or read, or that expired and can be put back, still never ends the process.
- The web dashboard now binds the account seed only inside the step that connects and mints its channel-purger cred. Before, the full connection, seed included, stayed in scope of the request handlers beside the narrowed copy they were meant to use, so a one-word edit in a handler could reach the seed and still compile. A handler that reaches for the seed now fails to typecheck.
What's Changed
- fix(cli): keep concurrent registry changes on an up refresh by @davidfarah2003 in #2609
- fix(core): drop stream-wide consumer delete from the observer, admin and agent profiles by @davidfarah2003 in #2610
- fix(manager): stop serving when another process holds the liveness lease by @davidfarah2003 in #2613
- fix(core): say in presence when a status was entered by @davidfarah2003 in #2616
- fix(manager): keep a manager on a divergent store off the daemon renewal lease by @davidfarah2003 in #2614
- fix(smoke): await broker exit before removing the store in card-host and ep-rail-failure by @davidfarah2003 in #2615
- fix(web): bind the account seed only where the purge cred is minted by @davidfarah2003 in #2617
- feat(auth): admit a logged-in user's hosted runs on a participant manager by @davidfarah2003 in #2619
- fix(core): date the presence activity apart from the status so cotal_roster shows its age by @davidfarah2003 in #2624
- feat(manager): bootstrap an already-enrolled lifecycle in a delegated child by @davidfarah2003 in #2627
- ci(smoke): gate every mutation config on the coverage validator by @davidfarah2003 in #2628
- feat(setup): share the user's Claude Code MCP servers with spawned sessions by @davidfarah2003 in #2629
- fix(manager): evict a restarting manager's credential family in one shared sweep by @davidfarah2003 in #2631
Full Changelog: v0.62.0...v0.63.0