Skip to main content
Mirrored from Meerkat Meta main at 26f5ae9da1fd on 2026-11-03; Meta version 0.9.9 at that commit. The Rust crate 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:
For identity-first runtimes with a roster provider, the Rust builder can enable identity-scoped agent memory injection. Open the SQLite authority explicitly and pass it through the provider seam:
The SQLite provider stores one database per realm. When a realm is first accessed and its SQLite connection is opened, it imports that realm’s legacy markdown files from the same agent-memory directory. Markdown is not a live provider. To supply another storage or retrieval authority, pass any 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:
The runtime mints one completion, on its bootstrap success path: the identity bootstrap on 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’s OperationRefusalObserver (see Observe refusal dispositions). Install it with the builder:
The builder installs it on every agent factory it owns, for persistent, scratch and temp-dir launches. Nothing of it reaches the event stream, a wire type or the model. A builder given an already-built 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.
When the review routes R3 to human consent (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_build runs 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, answers no_build_recorded until the member’s first build. Each of these calls gets the runtime’s real context: every registered identity as active_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_create is called after each seat, fresh life or migration that ran customize_build, with the session the identity is bound to, as at an identity’s creation. A wake’s build never calls it.
A respawn of a built member calls 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 answers Ok(None) to start without a customizer, or Err to refuse init.
  • Answering a customizer for None refuses init with -32603, and so does answering None for a composed customizer.
An application whose host gives members tools or other build-time surface through this hook, such as one that declares member classes, must therefore configure the SDK’s agent customizer. A pass-through is enough:
In TypeScript, .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:
With the declaration the mob’s member builds are wired when the mob is built, before it restores anything. A mob a killed process left running restores the members that owe work as it starts, before the identity bootstrap: each is held until that bootstrap’s roster pass publishes its customizer tools, with one 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):
Unset, 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 the runtime_options row fork_instruction_policy of the configuration reference). Set it with the builder:
The builder applies it to its mob for persistent, scratch and temp-dir launches, on create and on resume. Unset, Meerkat’s default (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:
The builder attaches a clone of the store to every agent factory it owns, for persistent, scratch and temp-dir launches, before anything clones the factory: the members and the agents they create, live sessions and the agent memory engines all resolve and persist provider credentials there, with one refresh authority. Pass the same root again whenever you reopen that user’s state. A builder given an already-built 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:
The member’s 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

The first function receives a diagnostic file label and document contents, not a state value or a file to read. The second accepts a Markdown table or YAML traceability document, including evidence for statuses that require it; it does not accept an array of status names. See Governance for complete examples and the validators’ limits.

HTTP routers

Build Axum routers for serving the console and interactions:
Binding is separate from routing. 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