MobMemberListEntry projections. Deep execution
inspection is a separate operation that returns MobMemberSnapshot.
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
RosterProvidercallback onrpc_gateway(Python.roster(provider), TypeScript.rosterProvider(provider)), or theidentity_rostertop-levelmeta/initparam onmeta_gateway(a list ofDurableAgentSpecobjects, restored at boot and extended at runtime bymeta/ensure_member); - a Rust
Discoveryimplementation onUnifiedRuntimeBuilder(classic, non-identity boot only; neither gateway binary wires one); - an explicit
meta/ensure_member.
orchestrator names a profile, not a member. The only automatic
spawn anywhere is meta_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.
On rpc_gateway, callback/roster_provider/roster supplies the serialized
RosterContext under params.context. Both SDKs pass a typed context to
roster(context): read context.mob_definition in Python or
context.mobDefinition in TypeScript for the booted mob’s compiled definition.
Its id, orchestrator, and profiles are top-level fields, unlike the
[mob] nesting in the source TOML. Prior-identity context is call-site-dependent;
an empty list does not establish that no identities are registered. See
Roster provider context for the field contract.
A host may instead derive a profile-shaped crew from its own parsed mob.toml
as an explicit policy choice, not as a workaround for a missing callback
definition. This example uses the same text passed to .mob_inline(MOB_TOML)
and intentionally ignores context:
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. Meta installs the definition’s wiring as a reconcilable edge
policy and converges it with meta/reconcile_edges (Python
handle.reconcile_edges(), TypeScript handle.reconcileEdges()).
meta/ensure_memberalready runs that reconcile after every successful ensure, on both the worker plane and the console identity plane. Callingreconcile_edgesafterensure_memberis redundant.meta/reconcile_identity(Pythonruntime.reconcile()) and the identity-first gateway boot do not: the gateway reconciles definition edges before the roster is materialized. When the definition declares wiring, callreconcile_edgesonce after boot and after eachruntime.reconcile().- Without declared wiring there is no definition edge policy; the call returns
an empty report. Edges from a host
TopologyProviderconverge on every identity restore and reconcile on their own. - With a
TopologyProvider, the provider (plus operator topology intent) owns the identities’ edges: reconcile unwires every live edge between two identities that neither the provider nor the definition’s wiring declares, including one wired before a restart. Declare an edge in your provider to keep it. Without a provider, reconcile unwires only the edges it wired 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:
The identity receipt’s
outcome is created, resumed, dormant,
refused 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 Meta worker-plane response has this
shape:
The lightweight entry deliberately omits session IDs and live connectivity
fanout. Use
meta/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_hostmember runs one kickoff turn as soon as it is spawned, on itsinitial_messageif the spawn carried one, otherwise on meerkat’s fallback prompt (You have been spawned as '<identity>' (role: <role>) in mob '<mob>'.). Thekickofffield 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. Meta
therefore skips ambient per-turn agent-memory recall for
autonomous_hostmembers with the typed reasonruntime_mode_autonomous_host(the turn still runs, without memory). Ambient per-turn memory requiresruntime_mode = "turn_driven"until meerkat’s carrier change lands; the Unreleased entry in the repositoryCHANGELOG.mdrecords this. turn_drivenmembers run exactly one turn per delivered input and sit idle between inputs. It is the mode meerkat’s tracked, completion-bearing turn carriers support; onautonomous_hostthose are refused withtracked turn completion is not supported by autonomous inbox delivery.
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.
Durable workers
A profile can declaredurable = true (meerkat 0.9.5):
DurableAgentSpec, meta/ensure_member
and the SDKs take no durable argument, and an omitted key means an ordinary
profile.
- Meerkat spawns the profile’s members with a durable identity, keyed by the member’s identity, so a host can list and re-attach them after a restart. Every identity-first member is already spawned that way, so for an identity the flag changes nothing in how Meta creates, wakes or resets it.
- A durable profile needs a mob whose member sessions outlive the process:
a runtime built with
persistent_stateor a storage provider. On any other mob Meerkat refuses the spawn before anything is created, whether the profile is inline or a realm profile:MobError::NonDurableService, codeCAPABILITY_UNAVAILABLE(-32020),data.kind = "mob_non_durable_service"with themember_idand theprofile. That includes Meta’s default composition: withoutpersistent_stateor a storage provider its member sessions live in memory, so it answers Meerkat’skeeps_durable_member_sessionsfalse.- On the identity plane the refusal is a deterministic build refusal. The
wake answers
HostRejectedBuildwith a reason that names the remedy: run the mob on a persistent realm, or dropdurablefrom the profile. Nothing retries it and nothing is recorded; every wake answers it again. - On the worker plane,
meta/ensure_memberanswers Meerkat’s own code anddatainstead of the generic-32602.
- On the identity plane the refusal is a deterministic build refusal. The
wake answers
- The mobpack editor document carries the flag as the member key
durable, present only when true. The export writesdurable = trueback into the profile, andupdate_memberaccepts a booleandurablepatch. Validation refuses a member whosedurabledisagrees withmob.toml(editor_profile_durable_mismatch) or is not a boolean (invalid_durable).
Resuming a durable worker’s run
Meerkat rebuilds a durable worker on its session after a crash, kill or restart, and continues the run it was in from its last committed round. Meerkat keeps no WorkGraph claim alive while the worker is down, so a worker that was down longer than its lease can come back to work that has moved on. Before such a run continues, Meerkat asks the mob’s resume check. It asks for every member spawned with a durable identity: the members of adurable = true profile, and every identity-first member, whose spawn always
carries one. When the mob runtime is composed with a WorkGraph service, Meta
answers the check from that WorkGraph:
- A run dispatched for an item (its input carries the WorkGraph attention projection) continues only while the item lives in the mob’s realm and is live, unclaimed or claimed by the worker under a lease that has not lapsed, and while its attention binding is active or paused and still targets the worker.
- A run that named no item continues unless a claim the worker holds has lapsed. A worker that holds no claim continues.
- Otherwise the run is superseded. Meerkat settles it the way a stop does: its bound inputs are abandoned as refused resumes, the run ends failed with the check’s reason, and it never resumes. A lapsed lease is committed first, with the WorkGraph’s lease-expiry observation, so the item can be claimed again, and every later attempt of the same resume answers superseded too.
- When the WorkGraph cannot be read, or the run’s attention projection does not decode, the check answers unavailable. Meerkat asks again, up to three attempts, then settles the run the same way.
agent:mob/<mob_id>/agent/<identity>) and by its session (session:<id>);
a claim under any other key belongs to another owner. Two limits apply. A
run that named no item, whose lapsed claim another worker took while it was
down, holds no claim any more, so the check cannot see the move and the run
continues; its later WorkGraph writes are revision-checked and conflict. And
the mobs that agents create through the mob tools have no check: their
durable workers continue unchecked.
Long-lived members an agent creates
An agent creates a long-lived agent as a mob member, never as an identity. Identities are declared by the host alone: a roster provider, theidentity_roster init param, or meta/ensure_member. The agent spawns the
member from a durable = true profile, into its own mob (mob_spawn_member,
or the member tool spawn_member) or into a child mob it creates
(mob_create, then mob_spawn_member).
- It survives a restart as a member of its mob. The roster pass retires
only the identities the roster materialized, so a member an agent spawned
is never retired because no roster lists it. It ends through its own
lifecycle: its work’s completion or supersession, an explicit retire, or
the idle-retirement policy when the spawn opted in with
idle_retire_secs(see Implicit delegate idle retirement). After a restart it is restored like every member of its mob: a member of the root mob is materialized at start only asmember_restoresays (see SDK and gateway runtime options), and otherwise on its first message or turn. - Talking to it. A member an agent spawns into its own mob with
mob_spawn_memberis wired to it by default: Meta setsauto_wire_parentwhen the agent omits it, so the two talk through comms, before and after a restart. The member toolspawn_memberwires the two only when the agent passesauto_wire_parent: true. The host reaches the member by its member id withmeta/send_message. - On a host store realm child mobs are process-bound and do not survive a restart, while members of the realm’s root mob do (see Keeping a Meta realm in your own database). There, an agent spawns its long-lived members into its own mob.
- What it lacks compared with an identity:
- identity status:
meta/status_identity,meta/inspect_identityand the identity bootstrap report do not know it; - a Console identity: it is not among the Console’s identities
(
/console/identities), and the identity-addressedmeta/console/senddoes not reach it (the Console still shows its spawn and its timeline); - agent memory, which is identity-scoped: no recall injection and no
memorytool; - identity delivery:
meta/send,meta/interactandmeta/dispatch, with their completion baselines, turn tickets and keyed sends, address identities only.
- identity status:
Deep member status
meta/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.
When Meerkat could not observe the member’s session for the status (meerkat
0.8.45+), preview_unavailable names why (observation_deadline,
read_failed, session_absent, not_observed_while_retiring), and a missing
output_preview then does not mean an empty member. meta/inspect_identity
and the console’s per-member progress carry the same marker.
The snapshot’s Rust agent_identity is bridge-internal and skipped during
serialization. The requested member ID identifies the result on the Meta RPC
surface. The snapshot also does not carry the roster entry’s role,
runtime_mode, wired_to, or labels fields.
Member statuses
The knownMobMemberStatus 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 withMobError::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 meta/init param. It is not a runtime_options field:
RoleMigrationDeclaration dataclasses or plain
{"identity": ..., "from_role": ...} dicts:
MobError::MemberRoleMigrationRejected on mismatch, so a mistyped from_role
cannot authorize an unintended restamp, and Meta 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.
The roster pass meets every identity Core holds under the role Core stored
for it, at a start and at a roster refresh alike, so a declared migration
first meets the member still on its old role. Meta then applies it or holds
it, and never adopts the member as it is:
- When the identity plane has repair custody (no Meerkat identity intent, or
an Absent one), Meta retires the member to absence, after proving the
session it will resume exists and carrying its queued input, resumes the
same session under the declared role, and releases the member again, so
its liveness is as before. Meerkat re-verifies the predecessor role, and
Meta checks that the member runs the declared role. A member no wake has
created a session for yet (seated, never woken) has no session to keep or
transcript to lose: the declared move rotates it onto a fresh session
under the declared role at the next generation, and its reconcile outcome
reads
created. - When the restored member’s role is not the declared
from_role, the declaration does not describe it, and Meta refuses before retiring anything. - When a Present Meerkat identity intent (from
mob/adopt_member_identity_declaration) owns the member, the intent pins its profile and MobMachine owns its materialization, so Meta refuses rather than retire it. Declare the new profile in the identity intent.
identity_role_migration_refused
(-32603), and the member is never retired for it: the identity stays a
member of Core’s mob under its previous role and reads active (an identity
Core holds no member for stays unseated and reads dormant), its
wake_refusal names the remedy resolve_role_migration, a delivery through
Meta is refused with that kind, and the meta/init result reports it under
identity_bootstrap (restore stage refused with that kind), so a held
migration is visible at boot. A roster refresh that moves an identity to
another profile releases the identity’s live member and then plans the
same way: it no longer retires the member, so the identity never leaves the
mob and keeps its session and generation. Core still holds the member under
its previous role, so a peer message Core routes to it directly wakes it
under that role. No pass retires the member the hold kept; each roster pass
and each delivery through Meta checks again, so declaring the migration
(next boot) or restoring the roster spec ends the hold.
On a runtime whose sessions live in memory only (one built with
MobBootstrapSpec::ephemeral, whose session service keeps no session beyond
its member), there is no session to carry over to the new role, and Core’s
release keeps such a member materialized. So a roster pass that moves an
identity Core holds to another profile neither releases it nor holds it: once
the member is idle, the pass rotates the identity onto a fresh session under
the new profile at the next generation, in one Core step that retires the
previous incarnation, whether or not a migration is declared. There is no
session continuity across a reprofile in this mode: the next turn, whether a
delivery through Meta or a message sent to the member on the mob, runs under
the new profile on the fresh session, and the previous role answers nothing
more.
A member with work in flight (a running turn, or input queued behind it) is
not rotated, so none of that work is cancelled: Core refuses the release the
pass asks for first, the work completes under the previous role, and a later
roster pass, or an explicit materialization or warm of the identity, rotates
it once it is idle. Inputs sent through Meta wait for the move; an input that
reaches the member outside Meta (a peer message, a send on the mob handle) in
the moment between Core’s idle answer and the start of the retirement ends,
typed, with the previous incarnation. A rotation Core refuses (another
lifecycle operation of the member is in flight) waits the same way. Until
then, deliveries through Meta and messages from peers still run under the
previous role; a delivery does not retry the move. A host that composes its
session bridge without the mob’s session service (MobSessionBridge::new)
cannot tell that the sessions live in memory only, so it keeps the release
and the hold described above, under which Core keeps the member materialized
under its previous role; compose it with
MobSessionBridge::with_session_service, as the gateways do.
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, meta_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 _. Meta’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::retirereturnsResult<(), MobError>.MobHandle::respawnreturnsResult<MemberRespawnReceipt, MobRespawnError>. The receipt identifies the respawned member.
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.
Meta’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 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 namedMemberSnapshot 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 meta/member_status.
- Python
- TypeScript
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 aRosterContext. 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_meta.identity_first_models; the TypeScript SDK as the exported
RosterContext interface (mobDefinition, previousIdentities). Use
context.mob_definition / context.previous_identities in Python and
context.mobDefinition / context.previousIdentities in TypeScript; the
prior-identity field has the same call-site qualification in both SDKs.
See also
- Identity-first doctrine - choose the identity plane or worker plane
- Python SDK - Python roster helpers
- TypeScript SDK - TypeScript roster helpers
- RPC API - JSON-RPC protocol and method signatures
- Unified runtime guide - reconciliation and session lifecycle
