Skip to main content
A temporary council seats forked branches of members that live in other mobs as ordinary members of a real, short-lived mob, runs a bounded sequential discussion, applies exactly one explicit merge-back policy, and destroys the temporary mob. Use a council when you need a second opinion from agents whose context you do not own: a release-readiness panel drawing on the incident room’s investigator, a design review seating the author from another team’s mob, or a cross-context adjudication between two long-running specialists.
Councils are orchestration, not a new agent subsystem. The temporary mob is a real mob created from your explicit definition through the ordinary create path. MobMachine, the member machines, and the forked-participant lifecycle machine remain the canonical owners of every mob, member, and capability decision.

Mental Model

Source-Owned Capabilities

A participant is not a copy of a member and not a new agent built from a prompt. The council asks the source owner for a forked-participant capability over a chosen prefix of the source member’s transcript, and seats that branch. Consequences worth stating plainly:
  • The source owner stays the authority. Tool, auth, realm, and filesystem boundaries remain those of the source execution context. The council request has no credential, auth override, or mutable session state field, so it cannot widen them.
  • The capability is held by reference. The council persists the capability reference so a crashed coordinator can still revoke it at the owning runtime — including a HOST-owned capability whose record lives in a remote host’s store.
  • No bearer material ever reaches a caller. Results and record projections carry non-secret provenance only: owner route, fork session, source session with its selected prefix length and prefix digest, granted scope, reuse policy, expiry, and a non-secret correlation hint. There is no wire field that could hold a capability id, bearer token, revocation id, or cleanup id.

Real Temporary Mobs

The council creates a real mob whose id is derived deterministically from the council id (council--<council_id>). That is what makes a retry after a crash find the same mob instead of creating a second one, and it is why the temporary mob appears in the same runtime’s ordinary mob registry while present, observable through the host mob/list operation. Observing that mob does not add a host or CLI endpoint for invoking a council. Your definition_template is used verbatim apart from its id, which is replaced with the deterministic council mob id. Every participant’s target_profile must exist in that template.

Bounds

Every bound is validated before any side effect: an over-budget request never creates a mob, a capability, or a turn.

Merge-Back Modes

Exactly one policy per council. No mode merges a transcript into the calling session: the only thing that reaches the caller is the sealed outcome itself (see Agent Tool).
There is no implicit transcript merge. If you want content back, ask for it with one of the explicit policies above. Beyond its sealed outcome, a council appends nothing to the caller’s session.

Durability And Crash Cleanup

Durability is an explicit declaration, never an inference.
  • durable requires a council store that actually survives a restart. If the serving runtime’s store is process-bound, the request is refused with capability_unavailable rather than silently promising crash recovery.
  • process_bound is the explicit opt-in for tests, embedders, and ephemeral surfaces. It says out loud that a process death loses the record and the source capability TTL is the only remaining backstop.
A coordinator that dies mid-council leaves a record with no result. The next council operation — or an explicit recovery sweep — seals it as a typed coordinator_interrupted terminal and runs cleanup. After a restart, every host whose council store is durable runs that sweep on its own, including a host that supplies a durable store without a persistent root, provided the host built its MobMcpState with MobMcpState::into_shared(). The sweep runs on its own task through the state’s weak self-reference, which only into_shared sets; a state wrapped in a plain Arc::new never runs it. A record whose previous claim lease has not expired yet is reported as held and retried once the lease expires, rather than skipped. Recovery never re-executes a council, because a re-run would duplicate model work and result delivery. Cleanup is reported separately from the result, and the two are never folded into one verdict: A sealed result with outstanding cleanup is a success carrying a cleanup status — never an error.

Host Bootstrap

A HOST-owned participant can only be seated once its owning host is bound into the temporary mob. A descriptor’s one-time ceremony token cannot be copied from the source mob and reused after it has been spent. For a host not yet bound to the temporary mob, the coordinator automatically asks the source owner for a fresh delegated host-binding descriptor scoped to the exact temporary mob id and its supervisor. It then binds that host before seating the participant. The source must authorize this handoff; automatic orchestration does not bypass source authority. Callers supply the council request, not a separate bootstrap object. Credential-like descriptor and delegated-proof material stays internal and is not exposed in results or persisted in the council record. If descriptor handoff or host binding is refused or fails, the sealed result reports a typed participant_seating_failed exit reason. Expiry of the remaining deadline reports deadline_exceeded; neither case fabricates a successfully seated participant. A participant forked from a member with activated host instructions is forked under the host’s fork instruction policy (see Mobs). A participant runs its first council turn as soon as it is seated, so under the default policy the fork is refused before anything is committed and the council ends with the typed participant_fork_instruction_refused exit reason. It carries the participant’s order and the typed refusal, and it is the same for local and host-placed sources. Nothing was created for the refused participant, so cleanup has nothing to revoke and records no cleanup debt for its slot. With reactivate_inherited the participant starts with its own activation of every key that was effective in its source. A member host older than this release cannot take the policy; asking it to re-activate ends the council with the refusal cause member_host_unsupported.

Idempotency

The council id is the idempotency key.
  • Same id + same request ⇒ join the running execution, or replay its sealed result.
  • Same id + a materially different request ⇒ refused as a conflict (duplicate_input), with both fingerprints in the error payload.
  • A second coordinator may take a council over only after observing the claim lease expired; the takeover advances the claim epoch and fences the previous executor (stale_fence).
Caller cancellation abandons the response, not the execution: the council runs to its deadline, seals its result, and cleans up regardless.

Agent Tool

Temporary councils are invoked by agents through the council mob tool, beside delegate and fork_off. They are deliberately absent from CLI, REST, JSON-RPC, public MCP, and generated SDKs: convening a council is an agent decision-support action, not a human-operated lifecycle surface. The caller names existing source members and the question to decide. The tool resolves each member’s current profile and placement, synthesizes the explicit temporary MobDefinition, derives stable branch identities, and starts the council. Each participant is seated from the source mob the caller was admitted to: when that mob is destroyed and another is created under its id before the participant is seated, the council refuses the participant with the typed participant_source_replaced exit reason and forks nothing of the new mob. How the sealed result reaches the caller depends on the host:
  • Detached (hosts that outlive the call). A host with a runtime adapter (the JSON-RPC, REST, and MCP servers, Meta), rkat run --keep-alive, and rkat mob deploy --surface rpc can deliver a completion after the call returns, provided the convener can be made live again to receive it: it is a mob member, or the host installs a DetachedOwnerHost (the JSON-RPC host does). There the call returns at once with {"status": "running", "council_id": ..., "job_id": ..., "note": ...}. When the council ends, its sealed outcome (result, cleanup, replayed) is recorded once in the convener’s transcript as a durable BackgroundJob system notice (the header “Background council job <job_id> finished (completed or failed):”, with the outcome as the typed block’s detail and persisted: true). It is committed once on the council’s custody record and submitted as a continuation, durable from submission, then applied as a runtime input with the idempotency key council:<job_id>: a convener in the middle of a turn sees it at that turn’s next model call, saved once and with no second turn (one follow-up turn only if the turn ends before another model call), and an idle convener runs one wake turn that sees it. A convener that is no longer live (the runtime retired its idle executor) is revived when the continuation is applied: through its mob when it is a mob member, or through the host’s DetachedOwnerHost when it is a plain session. The status is failed when the exit reason is a failure (participant_seating_failed, participant_fork_instruction_refused, participant_source_replaced, wiring_incomplete, exchange_failed, coordinator_interrupted); the record still carries the full sealed outcome, and bound-limited ends (max_exchanges_reached, deadline_exceeded) complete normally with their partial result. The notice survives later model calls, turns, and reloads and is readable through the convener’s session history.
  • Blocking. The call waits for the council and returns the sealed outcome as its ordinary tool result, with blocked_because: host_declared_unavailable on a host that may exit when the turn ends (the rkat CLI unless it stays alive, such as rkat run without --keep-alive); no_runtime_adapter for a host that declares delivery without a runtime; no_continuation_owner for a host that never bound its runtime delivery inbox; or no_owner_revival_host for a convener that is a plain session on a host without a DetachedOwnerHost (a top-level REST, MCP-server, or rkat run --keep-alive session). No detached job is owed, so no result can be lost.
In both forms the council’s own timeout_seconds bounds it. The agent loop’s default tool deadline does not cut the call, so a council longer than that deadline still delivers its result; an operator’s explicit tools.tool_timeouts.council entry still applies. A detached council’s convener also hears back across a restart. The council’s custody record carries the convener’s job. After the post-restore recovery sweep, every council from an earlier process whose job is not yet settled has its outcome delivered: its sealed result when it finished before the restart, or the typed coordinator_interrupted outcome once the dead coordinator’s claim lease is observed expired. Councils are never re-executed, and delivery uses the same idempotency key, so the outcome is recorded once. A council whose convener session no longer exists (deleted or archived) is reported as owner gone and settled, so later restarts do not retry it.

Example Tool Call

council_id is optional. Omit it for an id derived from the tool call (agent-<uuid>); provide one when the agent needs an explicit idempotency key across retries. A council id may contain only ASCII letters, digits, -, and _, because it is embedded in the temporary mob id and in every participant’s comms name; any other character is refused before the council starts. Stored council records whose ids contain . or : still load. The default merge is a bounded summary by the last participant. Other policies are structured, selected_exchanges, artifact_reference, and no_merge. The tool schema advertises the bounds the tool enforces: max_rounds 1 to 16, max_exchanges 1 to 64 (default: participants times rounds), max_result_bytes 256 to 65536, and timeout_seconds 1 to 86400 (default 300), the ceiling set by the forked-participant capability TTL. The tool is available only when the calling agent’s generated mob authority can create a temporary mob and manage every source mob in the participant list.

Errors

Malformed input, missing mob authority, and an unknown source member are ordinary tool errors returned by the call itself, before anything starts. Once a council is admitted, its structured result separates the discussion terminal from cleanup status. A repeated explicit council_id with the same arguments joins or replays the original run; the same id with different arguments is a conflict. On a detached host, a refusal that the council itself decides after the call returned (an over-budget bound, a council_id conflict, capability_unavailable for a durable request on a process-bound store) is recorded with {"error": ...} as its outcome with status failed. On a blocking host the same refusals are tool errors. Seating failures, wiring gaps, exhausted budgets, and elapsed deadlines are not errors: they are typed exit_reason values on a sealed result, so you still receive the exchanges that did happen and the provenance of everyone who was seated.