meerkat-meta is the primary interface for Meta. All other interfaces (JSON-RPC, SDKs, console) are built on top of it.
Installation
Cargo.toml
Quick start
Host-supplied modules are optional. This runnable baseline starts no module processes; add module declarations only for executables or MCP transports your host actually supplies.Core types
MetaConfig
Top-level configuration for the module runtime.ModuleConfig
Configuration for a single module.EventEnvelope
Timestamped event wrapper.UnifiedEvent
Agent or module event discriminator.Runtime functions
start_meta_runtime
Bootstrap a module-only runtime:
start_meta_runtime_with_options
Bootstrap with additional options:
UnifiedRuntime::builder
Builder for the combined mob + module runtime:
Arc<dyn AgentMemoryProvider> with .agent_memory(provider, config).
Rust callers can write, read, and delete durable records through the unified
runtime:
Bootstrap completion
A host that starts its own work once the runtime is up (a first recovery pass, a readiness signal) takes the runtime’s bootstrap completion and starts that work inside its publication, which never runs on a runtime that has begun to close:Ok (a held one on Activated), or
activate_without_identity_context on Ok. UnifiedRuntimeBuilder::build
passes through one of these, and a failed bootstrap mints nothing.
take_bootstrap_completion hands it out once; otherwise it answers
BootstrapCompletionUnavailable::NotCompleted or AlreadyTaken. A runtime
from a legacy constructor (UnifiedRuntime::bootstrap and the
bootstrap_with_* family) has no completion until
activate_without_identity_context returns Ok. Installing an identity
context withdraws a completion not yet taken, because the identity bootstrap
that follows mints again; attaching one leaves it as it is.
publish(f) runs f once, only while the runtime has not begun to close and
its mob still admits work. The runtime’s close (shutdown, a failed
bootstrap’s cleanup, or the retained teardown of a dropped composition) begins
under the same gate: a publication that has begun finishes first, and a later
one is refused with RuntimeClosing without running f. A mob whose
admission already closed refuses with AdmissionClosed. While f runs the
mob cannot close for being idle, but an explicit MobHandle::close_admission
can still land, so work that f spawns handles MobError::AdmissionClosed
like any other caller of the mob.
Keep f short and synchronous, because a shutdown that begins meanwhile waits
for it. It must not block on async work, take the host’s own close boundary
again, or shut the runtime down: the gate is not reentrant. If f panics,
the panic propagates and the runtime can still close.
On the stdio gateway (rpc_gateway), the driver takes the completion once the
stdout capacity for its Ready line is reserved, so the runtime then answers
AlreadyTaken. A host composing the gateway gets it from
GatewayBootstrapReady::take_runtime_completion and publishes it inside the
same boundary as the Ready. A Ready published without taking it drops it.
Operation refusal observer
A governed operation that Meerkat refuses reaches the model only as uniform feedback. A host sees which typed refusal Meerkat met, and what it did with it, through Meerkat’sOperationRefusalObserver (see
Observe refusal dispositions).
Install it with the builder:
mob_spec(...) refuses
the observer with UnifiedRuntimeBuilderError::ConflictingConfiguration, and
the public MobBootstrapSpec constructors take none: a host that builds its
own spec installs the observer on its own agent factory and passes a session
service built on that factory to MobBootstrapSpec::new.
A host composing the stdio gateway answers
GatewayHostComposition::operation_refusal_observer. The gateway asks once
per member factory, with the construction it offers compose_member_factory,
before any answer is installed. Ok(None) (the default) installs nothing,
and Err refuses init. The observer sees every agent that factory builds:
roster members, spawned helpers and forks, and the child mobs and councils
agents create.
observe is synchronous and must not block, and a panic in it propagates on
the deciding task (the member’s turn).
Operation review on a host-composed gateway
A host composing the stdio gateway can install Meerkat’s operation review (meerkat_core::approval::review::BoundOperationReview: its reviewer,
approval owner and deadline) on the gateway’s member agents by answering
GatewayHostComposition::operation_review. The gateway asks once per member
factory, with the construction it offers compose_member_factory and
operation_refusal_observer, before any answer is installed and before any
session exists on the gateway’s runtime. Each launch builds one member factory
on its own runtime, so the gateway asks once per runtime.
with_human_consent), the gateway
first installs the review’s consent owner-loss settlement, the approval owner
that issues that consent, on the gateway’s runtime, and then installs the
review on the factory. A review without a consent route is installed on the
factory alone. The review covers every agent that factory builds: roster
members, spawned helpers and forks, and the child mobs and councils agents
create. Ok(None) (the default) installs nothing, and Err refuses init. The
settlement goes on the runtime the gateway has just built, before any session;
should that runtime refuse it, init fails with
GatewayHostError::ConsentSettlementRefused.
Agent customizer on a host-composed gateway
A host composing the stdio gateway (rpc_gateway::run_persistent_with_host)
can wrap the gateway’s agent customizer by answering
GatewayHostComposition::compose_agent_customizer. When the init registers a
roster provider, the gateway asks once, after bind_runtime and before the
identity context is installed, with the customizer it composed: the SDK’s
agent customizer (.agent_customizer(...) in Python, .agentCustomizer(...)
in TypeScript), wrapped by agent memory when that is configured. The answer is
the customizer of every identity member build: the roster bootstrap’s and the
later ones, such as a reset’s fresh session. The default keeps the composed
customizer, and a gateway without a roster provider never asks.
A wrapper delegates after_create as well as customize_build. The gateway
calls them at these points:
customize_buildruns once when an identity is seated and Core held no member for it (a new identity, an identity a first-boot manifest row brings in, a hosted identity its host creates), when a reset or a delete starts the identity’s fresh life, and when a declared role migration moves it to its new role. That call publishes the identity’s customizer tools and records the customized spawn spec in the seat or rotation. It builds nothing, except where the seat step builds the member from it: a seat that revives the archived session of a member Core retired after it was built (a respawn, for example), and a declared role migration’s restamp of the member onto its session.meta/identity/resolved_tools, which reports the tools a build actually resolved, answersno_build_recordeduntil the member’s first build. Each of these calls gets the runtime’s real context: every registered identity asactive_peers, the roster’s managed edges, and the runtime services. It also runs at every build of the member (each wake Core makes), right before the build. A start over identities Core already holds calls it for none of them until they are built.after_createis called after each seat, fresh life or migration that rancustomize_build, with the session the identity is bound to, as at an identity’s creation. A wake’s build never calls it.
customize_build once and after_create
once, with the session the respawn keeps, and Core builds the member once,
with that call’s tools: Core keeps the retired member’s session archived, so
the respawn’s seat revives the member on it, and Core’s wake then finds the
member live. The same holds for a materialization, a warm or a delivery
whose seat step builds the member: the member stays live for it. The roster
pass asks Core to release a member its seat step built, so that identity is
then like a lazily seated one. A lazily seated identity (one seated with
nothing built yet) gets two customize_build calls before its first run:
its seat’s, then its first build’s. A respawn of a member that was never
built does the same: its seat calls customize_build, and its build calls
it again.
after_create is best-effort: the seat or build stands whatever it answers.
The gateway calls it once Core’s journal records the seat, with no lock of
the identity held, so once the init has settled an after_create can call
back into the gateway for the same identity; during the roster bootstrap the
gateway still answers init_in_progress. The call can come after the
operation that seated the identity has answered; the roster bootstrap’s own
seats are normally told before the init settles (a bootstrap whose journal
reads fail answers anyway, and the calls follow). Calls for different
identities run concurrently, up to 16 at once, in no order across
identities. Calls for one identity keep the journal’s order, also across a
restart: what a stopped gateway still owed an identity (its retirement hook,
say) comes before the calls this gateway’s own steps owe it, which then
follow the init in the background: the init never waits for what a stopped
gateway owed. A seat whose incarnation has ended by the time the gateway
reaches it (the identity was retired, reset or moved since) is not told: its
retirement is handed to the retirement hook instead.
Each call has a deadline of 30 seconds. A call that has not answered by then
is made again, at most three times in all; an error it answers is final. A
call that failed is recorded, reported to the error hook as
lifecycle_effect_failed (effect after_create), and not made again; one
that never answers delays later side effects by at most its bound. Calls are
attempted at least once: a gateway stopped between the seat and the call
(killed, or a shutdown whose share of the drain window closed first) makes
it at its next start when its metadata store is durable (rpc_gateway over
a state directory or a host store realm; meta_gateway keeps it in memory,
so there it holds within the process only), and one stopped during the call
may make it again, so key what it does by the session. The context’s model
is the build’s model, else the model of the profile when the mob definition
declares it inline; it is empty for a realm-bound profile the build sets no
model for. A call made at a later start reports the identity’s roster labels
and no system_prompt: the build that made the seat ended with the stopped
process.
AgentCustomizer::after_create has a no-op default, so a wrapper that
overrides only customize_build silently drops the SDK’s after_create
callback.
The hook can wrap the composed customizer, but it cannot add one. Whether a
customizer is composed is settled before the hook runs, because the registry
that keeps each member’s customize_build tools across rebuilds (a restart,
a respawn, each wake) exists only when one is composed. A
customizer added later would lose its tools at the first rebuild. So:
- When the init registers no SDK agent customizer and configures no agent
memory, the gateway offers
None. The host answersOk(None)to start without a customizer, orErrto refuse init. - Answering a customizer for
Nonerefuses init with-32603, and so does answeringNonefor a composed customizer.
.agentCustomizer({ async customizeBuild() {} }) does the
same. A host that needs a customizer and is offered None answers
Err(GatewayHostError::AgentCustomizerRequired), so init fails before the
identity bootstrap instead of starting members without their surface. The
init failure (-32603) carries the variant’s text, which tells the operator
to configure the SDK agent customizer and that a pass-through is enough.
Agent customizer on a legacy constructor
A runtime from a legacy constructor (UnifiedRuntime::bootstrap and the
bootstrap_with_* family) prepares its members’ builds as the builder and the
gateways do. Declare the agent customizer on the spec to cover every build:
customize_build, and its run continues only with them. From then on
customize_build runs right before each build of an identity’s member, and
the build carries the tools it published. A build made at the mob’s lift,
before the roster pass, is held the same way and customized after it.
The identity bootstrap (install_and_bootstrap_identity_first_context, or
bootstrap_installed_identity_first_context and its held variants) installs
the declared customizer as the identity runtime’s own. The identity runtime
and the IdentityFirstRuntimeContext carry that same customizer or none:
another one is refused there. The context is installed by then, but the
customizer, its tool registry and the build preparer are not, and the runtime
shuts down. Declare a customizer only for a runtime that bootstraps an
identity context: until that bootstrap, an identity member the mob builds is
held. activate_without_identity_context refuses a declaring runtime
(InvalidConfig), and MobRuntime::bootstrap refuses a declaring spec, since
neither would ever release those members.
UnifiedRuntimeBuilder composes its own (agent_customizer) and refuses a
mob_spec that declares one.
Without a declaration the identity bootstrap takes the customizer installed on
the identity runtime (IdentityRuntime::set_agent_customizer), else the one
the context carries, which becomes the runtime’s own. That covers the builds
from the mob’s lift on, so a clean restart (the mob stays stopped until the
identity bootstrap lifts it) is covered, but a crash resume is not: the
members a killed process left owing work are built at the start, before the
identity bootstrap, without their tools until their next build. Without any
customizer the identity bootstrap changes nothing.
Member restore
Every roster identity is a member of the mob from ready, and is materialized when work reaches it.member_restore sets what the mob materializes when it
starts (the values are in the runtime_options row member_restore of the
configuration reference):
MemberRestore::OnDemand applies. The builder also applies it to a
supplied mob_spec(...); a spec you build yourself takes
MobBootstrapSpec::with_member_restore.
Members’ comms identity root
A member on in-process comms has a key pair of its own, which its agent factory keeps under<root>/.rkat/session_comms_identity, where <root> is
the factory’s AgentFactory::user_config_root (the builder’s
session_identity_config_root(root) sets it), or under the platform’s
per-user default when the factory has none. The mob seats each dormant member
under the root its session service reports for its agent builds
(MobSessionService::session_comms_identity_root), so the member’s first
materialization loads the key its seat recorded. A host that builds its own
spec over a session service on its own factory declares nothing: the service
reports the factory’s root.
MobBootstrapSpec::with_session_identity_config_root(root) overrides that
report, for a service whose report is not where its builds keep the key
pairs. A declared root that differs from the reported one wins, and the
bootstrap logs a warning naming both: a member whose first build loads its
key pair from elsewhere is refused at its first materialization. A session
service that reports no durable root has every seat refused until a root is
declared. A session service or agent builder that wraps another forwards
session_comms_identity_root to it; the method has no default, so a wrapper
without it does not compile.
A host’s own MobSessionService wrapper also forwards the member
operator-instruction methods (record_member_instruction_target,
release_member_instruction_target, read_member_instructions) and
bind_seated_member_session, as Meta’s own wrappers do. Their defaults
refuse typed, so a wrapper that keeps them blocks its members’ operator
instructions and refuses a dormant resume seat over a session the host
wrote. It forwards runtime_turn_lost_its_host_runtime and
live_session_actor_registered the same way. See Meerkat’s
upgrade guide.
Fork instruction policy
A fork of a member with activated host instructions does with them what the host’s fork instruction policy says (the values are in theruntime_options
row fork_instruction_policy of the
configuration reference). Set it with the builder:
RequireExplicitActivation) applies. A builder given an already-built
mob_spec(...) refuses it with
UnifiedRuntimeBuilderError::ConflictingConfiguration: set it on that spec
with MobBootstrapSpec::with_fork_instruction_policy.
The mob does not store the policy, so the builder applies it on every create
and resume, and it cannot be chosen per fork. Under the default, a fork you
drive yourself with UnifiedRuntime::fork_member is committed, and its child
runs no turn until you activate each inherited key on it with
runtime.mob_handle().activate_member_instruction(child, request); read the
key’s key_state with read_member_instruction_activations first for the
expectation and supersedes to use. A fork that starts its own first turn
(fork_off, a delegation fork, a council participant, the bounded and
detached member forks) is refused with MobError::ForkInstructionRefused
instead. See Fork instruction policy
for what a fork copies and what ReactivateInherited trusts.
Provider credential store
By default every agent factory keeps provider credentials (persisted API keys and the OAuth tokens a login saved) in Meerkat’s process default store,<config dir>/meerkat/credentials, which every runtime and process of the OS
user shares (see Provider credentials).
A host serving several users opens a store per user and hands it to the
builder:
mob_spec(...) refuses the store
with UnifiedRuntimeBuilderError::ConflictingConfiguration, and the public
MobBootstrapSpec constructors take none: a host that builds its own spec
attaches the store to its own agent factory with
AgentFactory::with_provider_auth_persistence, passes a session service built
on that factory to MobBootstrapSpec::new, and gives memory engines it
assembles itself the same store with
memory_wiring::attach_memory_engines_with_provider_auth.
A host deploy’s rkat mob run process cannot carry the store, so a runtime
built with it refuses meta/mobpacks/deploy with execute: true on its RPC
and its Flow Editor (-32004, data.kind = "provider_credentials_not_carried"). A host that attached the store to its
own spec’s factory records it with
runtime.set_explicit_provider_credentials(true) before it builds routers.
A host composing the stdio gateway answers
GatewayHostComposition::provider_auth_persistence. The gateway asks once per
launch, before any agent factory is built, and attaches the store to its
member factory before compose_member_factory is offered it, so the
construction’s host auth service and realm MCP installation use it too.
Ok(None) (the default) keeps the process default, Err refuses init, and
runtime_options.provider_credentials_root is refused under a native host.
Session hooks and mob operator authority
A session hook (SessionHook, installed with .session_hook(...)) runs
before_create on every build of a session: a member’s first build and each
rebuild of its session, including the restore of a member after a restart.
An edit the hook makes to the request applies to that build only.
A member whose profile enables tools.mob gets Meerkat’s default mob operator
authority, which may spawn every profile of its mob. A host narrows it in the
hook, on the build’s mob_tool_authority_context, and leaves alone a build
whose authority holds no scope in its mob (a member of a child mob, for
example). The sample uses meerkat-core, meerkat-runtime and async-trait
as direct dependencies besides meerkat-meta:
spawn_member and spawn_many_members then refuse any other
profile of that mob with access_denied. A batch that names one is refused
whole: none of its members spawn.
The narrowing is per build, and so is a disable of the member’s mob tools
made in the hook. Neither is restored as authority: a restore build reaches
the hook with the profile’s setting and the default authority again. Make the
edit on every build, restores included, or a restored member returns to the
default authority. A hook that also installs host tools composes them over
the session’s own tools
(meerkat_meta::tool_compose::ComposedExternalTools::over), so the member
keeps its mob tools and the authority Meerkat binds on them.
Module routing
route_module_call
Route a typed call to a module:
route_module_call_rpc_json
Route a JSON-encoded request and receive a JSON-encoded response:
Operational subsystem functions
Decisions
build_runtime_decision_state is the validating constructor; it checks the
trusted OIDC snapshot only when console.require_app_auth is true.
RuntimeDecisionState::local_console is the explicit opt-out for local or
host-protected consoles; at serve time the runtime reads console, the
session-store naming and modules, and a host with trusted modules adds them
with .with_modules(...). The default stays fail-closed: ConsolePolicy::default()
requires app auth, and a state that trusts no key refuses every request. See
Decisions for every check and
when it runs.
gateway_composition also exports the HTTP exposure vocabulary both binaries
use: GatewaySurface (RpcGateway | MetaGateway, Display is the binary
name), ConsoleAuthPosture::of(&decisions) (Open | ClosedToEveryCaller |
Enforced, the one classifier behind the bind gate and the non-loopback
warning), HttpBindPolicy, validate_http_bind_policy(surface, listen, policy),
warn_on_non_loopback_bind(surface, bound, &decisions), and the typed
HttpExposureParseError behind parse_http_listen_addr,
parse_http_public_base_url and parse_http_listen_arg. The error enums
(HttpBindPolicyError, HttpExposureParseError, GatewayHostConfigError,
HostConfigTableInMobToml) are #[non_exhaustive].
Authentication
Governance
HTTP routers
Build Axum routers for serving the console and interactions:GatewayHttpBinding::bind_loopback() is
the default posture (loopback, ephemeral port) and
GatewayHttpBinding::bind(addr) takes any SocketAddr; http_base_url()
returns the same-host form, mapping wildcard binds to loopback of the same
family: 0.0.0.0:PORT becomes http://127.0.0.1:PORT, and [::]:PORT
becomes http://[::1]:PORT. Concrete bound addresses and the bound port are
preserved. with_advertised_base_url(..) carries a proxy-facing base for
your init reply.
Before binding, validate the requested addr: SocketAddr against your launch’s
allow_remote acknowledgement and decision state:
GatewaySurface is the closed gateway diagnostic enum, not an arbitrary
host name; use GatewaySurface::MetaGateway instead when representing that surface.
The check applies the gateways’ fail-closed rule: non-loopback needs enforced
console auth or the allow_remote acknowledgement. After binding, call
warn_on_non_loopback_bind(surface, bound, &decisions) for the gateways’
WARN line. These helpers and GatewayHttpBinding live in
meerkat_meta::gateway_composition. See Deployment.
See also
- Quickstart — getting started
- Architecture — crate structure and design
- TypeScript SDK — TypeScript equivalent
- Python SDK — Python equivalent
