Skip to main content
This page documents MobKit v0.8.34 (mirrored from v0.8.34). The mob roster is the operational view of the members known to a mob. Roster reads return lightweight MobMemberListEntry projections. Deep execution inspection is a separate operation that returns MobMemberSnapshot.
Member lifecycle is dual-plane. Use the worker plane for ephemeral, disposable members such as helpers spawned by a parent agent and reaped by an idle-retire policy. Durable members, including per-user agents and long-lived coordinators, belong on the identity plane: declare them in an identity roster and reconcile them. The gateway profile changes the default behavior of mobkit/ensure_member; see the profile-specific contract below.

Profiles are templates and members are declared

A [profiles.<name>] entry in mob.toml is a role template: model, tool posture, skills, runtime mode, peer description. It produces no member. A member exists only when something names it:
  • the identity roster: the RosterProvider callback on rpc_gateway (Python .roster(provider), TypeScript .rosterProvider(provider)), or the identity_roster top-level mobkit/init param on mobkit_gateway (a list of DurableAgentSpec objects, restored at boot and extended at runtime by mobkit/ensure_member);
  • a Rust Discovery implementation on UnifiedRuntimeBuilder (classic, non-identity boot only; neither gateway binary wires one);
  • an explicit mobkit/ensure_member.
Nothing derives one member per profile, and neither does meerkat: a mob definition’s orchestrator names a profile, not a member. The only automatic spawn anywhere is mobkit_gateway’s no-config fallback, which ensures a single alpha member of its built-in definition when no config/mob.toml or definition.json exists; a workspace definition with an empty identity_roster boots with no members at all. The roster callback does not hand you the definition. On rpc_gateway the gateway sends callback/roster_provider/roster with the serialized RosterContext (mob_definition, always the definition the gateway booted with, and previous_identities) as the request params, but the Python and TypeScript SDK dispatchers pass the provider params["context"], a key the gateway does not send. The context argument your roster(context) receives is therefore an empty dict or object on both SDKs, and context["mob_definition"] raises KeyError. A host that wants a profile-shaped crew derives it from the definition it already holds, the same mob.toml text it passes to .mob_inline(...) (HomeCore builds its roster the same way, from its own parsed config, and ignores context):
Profile-to-member is not one-to-one in general (a per-user profile may back hundreds of identities), which is why this stays a host decision rather than a default.

When to call reconcile_edges

Declared definition wiring ([wiring] auto_wire_orchestrator = true or role_wiring rules) is applied by meerkat only at spawn time and, for the orchestrator rule, only from the non-orchestrator side, so the result depends on bring-up order. MobKit installs the definition’s wiring as a reconcilable edge policy and converges it with mobkit/reconcile_edges (Python handle.reconcile_edges(), TypeScript handle.reconcileEdges()).
  • mobkit/ensure_member already runs that reconcile after every successful ensure, on both the worker plane and the console identity plane. Calling reconcile_edges after ensure_member is redundant.
  • mobkit/reconcile_identity (Python runtime.reconcile()) and the identity-first gateway boot do not: the gateway reconciles definition edges before the roster is materialized. When the definition declares wiring, call reconcile_edges once after boot and after each runtime.reconcile().
  • Without declared wiring there is no definition edge policy; the call returns an empty report. Edges from a host TopologyProvider converge on every identity restore and reconcile on their own, and the reconciler never unwires an edge it did not wire itself.

Roster operations

All operations are available through the gateway’s JSON-RPC interface.

ensure_member gateway profiles

The same method name has different ownership and result contracts across the two gateway profiles:
To create an ephemeral worker through an identity-first console/admin gateway, you must pass plane: "worker". Omitting it creates or retains a durable identity and returns an identity receipt, not a MobMemberListEntry.
The identity receipt’s outcome is created, resumed, dormant, broken, or unchanged. The other roster operations keep the contracts shown in the operation table.

Lightweight roster entries

list_members, get_member, and find_members project Meerkat’s MobMemberListEntry. ensure_member projects the same type only on the worker-plane paths described above. A raw MobKit worker-plane response has this shape:
The lightweight entry deliberately omits session IDs and live connectivity fanout. Use mobkit/member_status when those details are required.

Runtime modes

runtime_mode comes from the profile ([profiles.<name>] runtime_mode), or from a per-spawn override where a surface offers one. autonomous_host is meerkat’s default when a profile omits the key, and it is the mode most hosts end up on without choosing it. The two modes differ in ways that show up on the roster and on every send:
  • A fresh autonomous_host member runs one kickoff turn as soon as it is spawned, on its initial_message if the spawn carried one, otherwise on meerkat’s fallback prompt (You have been spawned as '<identity>' (role: <role>) in mob '<mob>'.). The kickoff field on the roster entry reports that turn. Later sends are ordinary durable inputs that queue behind it; nothing is dropped, but the member answers in order.
  • Autonomous inbox delivery carries no user-channel work boundary, so meerkat refuses injected context and completion-bearing sends on that path. MobKit therefore skips ambient per-turn agent-memory recall for autonomous_host members with the typed reason runtime_mode_autonomous_host (the turn still runs, without memory). Ambient per-turn memory requires runtime_mode = "turn_driven" until meerkat’s carrier change lands; the Unreleased entry in the repository CHANGELOG.md records this.
  • turn_driven members run exactly one turn per delivered input and sit idle between inputs. It is the mode meerkat’s tracked, completion-bearing turn carriers support; on autonomous_host those are refused with tracked turn completion is not supported by autonomous inbox delivery.
Pin runtime_mode = "turn_driven" explicitly on every profile that hosts a durable, addressable identity unless the member is meant to run its own inbox loop. Operational list projections retain active, retiring, and broken rows as applicable. They filter terminal completed and unknown rows, even though those values remain part of the status vocabulary.

Deep member status

mobkit/member_status serializes Meerkat’s MobMemberSnapshot. It is a deep inspection result, not the type returned by roster list operations. Its core fields are status, output_preview, error, tokens_used, is_final, and current_session_id. It can also include peer connectivity, kickoff, external-member observations, resolved model capabilities, execution progress, placement, reachability, freshness, and lifecycle-capability diagnostics. The snapshot’s Rust agent_identity is bridge-internal and skipped during serialization. The requested member ID identifies the result on the MobKit RPC surface. The snapshot also does not carry the roster entry’s role, runtime_mode, wired_to, or labels fields.

Member statuses

The known MobMemberStatus values are:
MobMemberStatus is non-exhaustive. Branch on the known values and tolerate new status strings. Do not reduce lifecycle handling to only active and retiring. The Rust MobMemberSnapshot structure is also non-exhaustive.
retiring can be transient. At the Meerkat Rust layer, a successful MobHandle::retire is the terminal lifecycle barrier and returns unit, so a later roster read can show the member as absent rather than retiring.

Role migration

A durable member’s role is part of its durable identity, not a configuration knob: the stored role, the member’s comms name, and its mob-member binding are one fact. A resume that targets a different role than the durable predecessor is therefore a restamp of identity, and Meerkat refuses it with MobError::MemberRoleMigrationRequired unless that exact resume declares the migration. If a durable member stopped resuming after a role or profile rename, this is the refusal to look for. Migration authority is supplied by the activation, per exact identity, as a top-level mobkit/init param. It is not a runtime_options field:
Python hosts declare the same thing on the builder, with RoleMigrationDeclaration dataclasses or plain {"identity": ..., "from_role": ...} dicts:
The declaration is boot-scoped and never persisted. It is installed on the session bridge, which lives for exactly one boot, so dropping it from the next boot payload is how the authority goes away. Lookup is an exact identity match: no prefix, suffix, or case variant of a declared identity inherits authority. MobKit only carries the declared predecessor role into the resume request. Meerkat re-verifies it against durable state and refuses with MobError::MemberRoleMigrationRejected on mismatch, so a mistyped from_role cannot authorize an unintended restamp, and MobKit never retries a resume that Meerkat refused. Once the durable and requested roles agree, Meerkat returns before it reads the declaration at all, so a declaration left in place after the migration landed is inert rather than a repeated restamp. That is why an identical repeated declaration is accepted on purpose, while one identity declared twice with conflicting predecessor roles is refused.
A malformed or self-contradicting role_migrations payload refuses the boot rather than arming nothing, and the two gateway binaries refuse differently. rpc_gateway parses and checks the key at init scope and answers both refusals with -32602 on the request id. mobkit_gateway answers with a null id in both cases: -32602 when its typed init params fail to deserialize, and -32603 from the conflict check that sits inside its identity-first block. Under identity_first: false that block never runs, so a self-contradicting payload is then not refused at all; identity_first defaults to true. The Python builder raises ValueError on a conflicting pair before any of this reaches a gateway.
Only the identity plane carries migration authority. Every mob-plane resume path passes no declared predecessor role, so a role change reached through the worker plane still fails closed. Which declarations arm also depends on the binary: rpc_gateway installs them inside its roster-provider branch, mobkit_gateway inside its identity-first block. Declarations that reach neither arm nothing, because there is no identity plane to migrate on.

Peer identity and comms names

A member’s comms (peer) name is {mob_id}/{role}/{member} (meerkat’s MemberCommsName): the mob definition id, the profile name, and the mob-roster member id. That is the string peers see in the peers tool and in Peer message from ... projections; the routing key underneath is the pubkey-derived peer id. Identity-first members are registered under an encoded roster id, because a durable identity such as domain:security contains : and a comms-name component may only contain ASCII letters, digits, -, and _. MobKit’s mk-- codec escapes it (domain:security becomes mk--domain_csecurity), so the peer-facing name is home/domain/mk--domain_csecurity. Console, RPC, and SDK surfaces decode the id back to the public identity; the model-facing projections do not. The mob id is part of every member’s name, so renaming a mob (changing the definition id) re-mints every peer name in it. Nothing rewrites records that already hold the old strings: identity-keyed agent memory lives in the host realm, survives the rename, and can recall {old_mob}/... names into a later prompt. Renaming a member’s role is a different case and is refused unless declared, as the previous section describes.

Mutation results

The underlying Meerkat Rust operations have different success types:
  • MobHandle::retire returns Result<(), MobError>.
  • MobHandle::respawn returns Result<MemberRespawnReceipt, MobRespawnError>. The receipt identifies the respawned member.
The domain MemberRespawnReceipt publicly serializes only identity; its runtime and fence atoms are internal. Meerkat’s typed mob/respawn surface wraps that outcome as status (completed or topology_restore_failed), a receipt containing identity and an opaque member_ref, and optional failed_peer_ids. MobKit’s compatibility RPC consumes the worker-plane respawn receipt and projects both mutations as acceptance acknowledgements. Identity-owned targets can add identity-first receipt fields such as a fencing token for retire, or a session ID and generation for respawn. The Python and TypeScript SDK wrappers discard these acknowledgement objects and return None or void. Re-read the member when the caller needs the resulting roster or execution state.

SDK projections

The SDK type named MemberSnapshot wraps the lightweight roster response. It currently exposes agent_identity, role, state, wired_to, and labels. It does not expose every field present in the raw MobMemberListEntry JSON. The separate RichMemberSnapshot type wraps mobkit/member_status.

Reconciliation

Reconciliation compares the desired member set with the current roster and spawns, retains, or retires members to converge. See the unified runtime guide for the complete flow.

Roster provider context

Every time the identity runtime derives the desired roster it calls the host’s roster provider with a RosterContext. Behind the SDK gateway that is the callback/roster_provider/roster request, whose params nest the context under context (the same envelope the topology and customizer callbacks use):
The Python SDK delivers this as RosterContext from meerkat_mobkit.identity_first_models; the TypeScript SDK as the exported RosterContext interface (mobDefinition, previousIdentities).

See also