agentplane

Status

What is pre-alpha in agentplane, which surfaces to pin, what is deliberately not built, the format-freeze conditions, and how to check any of it yourself.

On this page
  1. 📌 What to pin, and what will move
  2. 🧊 Format freeze: the conditions, and where they stand
    1. What the freeze is a promise about
  3. 🚫 Deliberately not built
    1. Refused
    2. Deferred
  4. 🔍 How to check any of this

agentplane is published on crates.io and pre-alpha. This page answers three questions an adopter has to answer before writing any code: what will move, what is deliberately absent, and how to check that either answer is still true. What exists is answered by concepts, the API reference and the test suite; what changed is in the changelog. Evaluating against a control catalogue → evaluator questions.

📌 What to pin, and what will move

Pre-alpha means every one of these can change. It does not mean they are all equally likely to, and an adopter deciding what to build against deserves the difference rather than one blanket warning.

Nothing here is a compatibility promise. It is a statement about where the remaining design pressure is, grouped by what the freeze will be a promise about.

SurfaceExpectWhy
What the freeze will cover
Effect / disposition / recovery vocabularystableDidNotHappen/InDoubt/Landed and the recovery classes are the load-bearing idea; changing them would be a different system
Journal record formatwill changenot frozen. Every shipped reader reads through an upcaster, but the freeze has not been performed and hard cuts are preferred until it is — see the freeze conditions below, and the export-as-the-durable-artifact position in the meantime
Additive under the freeze too — a new field, never a removed one
Manifest schemaadditive, with hard cutsdeny_unknown_fields makes an added field safe and a removed one a hard failure. The published JSON Schema is generated from the parser’s types and moves with them
Outside the promise — pin an exact version
Operator API (OpenAPI document)will changegenerated from the routes and the types they answer with, so it moves with them; generate a client per version → the operator surface
Error enumsadditive; #[non_exhaustive]they gain variants as the runtime learns to say more — a rate limit is its own variant rather than a generic rejection, and the next distinction will be too. Match with a _ arm
RetryPolicy fieldsadditiveit is a plain struct, so a literal breaks when a field lands. Build with RetryPolicy::attempts(n) and the builder methods, or spread ..RetryPolicy::default()
Policy seam (PolicyEngine, request context)stable seam, growing contextthe trait is settled; context gains attributes as the runtime learns to say more. Guard optional ones with has → security
Store traitsstable seam, growing contractthe conformance battery is the contract; it gains cases faster than the traits gain methods
testkitstable, additiveit is how embedders test their own stores and skills, so churn here costs more than it saves
Skill, StepCtx core methodsstable in shape, additivenew capabilities arrive as new methods; existing ones are not expected to change signature
Runtime admission methodssettledevery door takes Tainted<Value> — wrap an operator’s own literal in Tainted::trusted(..). run_in_case means this exact case; correlating is run_correlated
Tool / ToolFailuresettledTool::call returns ToolFailure, named by disposition rather than transport; references are tool://server/name
Effect keyswill changeany change to a descriptor’s arguments moves every key for that effect kind. A reference names a server and a tool, never a transport, so the key does not move when the transport does
Store schemas — SQL, redb, and what a blob store writeswill change without migrationthere is no migration tooling: recreate rather than migrate. Widening a redb table fails at open rather than at read, and a tombstone carries a version a later build refuses rather than guesses at
A2A / MCP wire behaviourtracks the specsexact released revisions, not a compatibility range: a revision this crate has not been held to is one it does not claim, and the serving and calling roles differ → which revisions this plane speaks

🧊 Format freeze: the conditions, and where they stand

The single question that decides whether this can be recommended for anything regulated, asked directly enough to deserve a direct answer: the strongest control here — a tamper-evident, offline-verifiable audit trail — cannot be signed off as a long-term record while its format may break with no migration path. Every other gap an adopter finds is closable with integration work. This one is not.

There is no date. What there is instead is a condition list, each row naming what it demands and where the evidence is.

Every condition is met. The freeze itself is a deliberate act — the point at which hard cuts stop and a shape change becomes an upcaster plus a version bump — with no target release. It lands together with what holds a frozen vector, which is not yet in place: a check that every build still reads and hashes the vectors earlier builds wrote, a corpus that only gains vectors, and the operator vocabulary’s spellings pinned as the promise states them.

#ConditionWhere the evidence is
1Canonicalization is versioned and vector-checked — a rule change must read as unverifiable, never as a divergenceRFC 8785 held to the standard’s own number vectors → canonical JSON
2Golden corpora for the journal record format — a fixed set of records every future build must still read and still hash identicallytests/golden/records.jsonl, one record per kind, which this build writes byte for byte, with a guard holding the corpus to the record vocabulary. That a later build still reads them is held from the act on
3Golden vectors for the export format — the artifact a third party verifies without this cratetools/verify_export.py, re-deriving all 33 record vectors from the specification alone
4A stated unknown-field policy per durable formatper format, not once — a record body, a sealed header and an export’s framing each say what they refuse → format
5Upcasters exercised end-to-end, not only unit-testedboth stores, verify, restore and export read through an upcaster, and the store battery reads an older record shape back lifted with its bytes as written → versioning
6A migration and rollback procedure, written down and rehearsedreaders before writers, the rollback window closing at the first write of a bumped kind, rehearsed in-tree over an export one record shape older than this build writes
7An algorithm-agility plan — how SHA-256 is replaced without invalidating historywritten down: hashes agile by version, signatures by key, and nothing rehashes stored bytes
8The deferred format questions are settlednone is open as a question: each parked record change, and each open item whose specification would move a durable format, is collected as a cost of the act, which a freeze performed first prices at an upcaster rather than a cut
9The surface the promise attaches to is namedthe artifacts, not either doorway — below

What the freeze is a promise about

Both surfaces exist and are published — a crate an embedder links, and a server an operator runs against a manifest with no Rust anywhere — and they carry the same guarantees. So the promise does not attach to either doorway. It attaches to what the guarantees are made of:

Under the freezeTerms
The journal record formatyesa shape changes by upcaster and a version bump, never by a hard cut
The export formatyesthe artifact a third party verifies, against a published specification a second implementation already reads
The operator verbs and their vocabularyyesan outcome, a disposition, a recovery class and a run status keep their spelling; a verb gains arguments and does not lose them
The manifest schemaadditivean added field is safe by construction, a removed one is a hard failure
The Rust API — Skill, StepCtx, Runtime, the store traitsnopre-alpha and additive in intent, pinned by an exact version in practice
Store schemasno, deliberatelythe journal is the record and a store is an index derived from it; a store is rebuilt from an export rather than migrated

What an adopter depends on is the evidence, and both doorways produce the same evidence. Linking the crate gets the same promise about everything it stores and exports, and takes the ordinary pre-alpha risk on the signatures it compiles against — the half a cargo update reports and a rebuild settles, rather than the half that strands a history.

Until the freeze, the honest position for an adopter is: treat the export as the long-term artifact and the store as disposable. agentplane export produces framed JSON Lines with a checkpoint, agentplane verify recomputes it from its own bytes, and agentplane restore rebuilds a store from it — three verbs that already work, and the reason a store change is a rebuild rather than a loss — the procedure is on upgrading. That is a real answer, not a promise: an export taken today is verifiable today by a party who has never run this crate.

🚫 Deliberately not built

Two kinds, and conflating them is what makes a gap list useless. Refused will not arrive: something in the design forecloses it, and the entry says what. Deferred names what it is waiting on, so a reader can tell whether their own need would move it.

Refused

RefusedWhat forecloses it
Cross-run mutual exclusion over a declared resourceAn effect group enforces its footprint within a run. Two runs grouping over the same resource are ordered by the resources themselves, not by the plane
Speaking ACS as an observed agent — asking an external Guardian to permit each stepACS-Core’s failure posture defaults to proceed for a hook that times out and a handshake that never completes, so an adversary who can disrupt the channel converts control into audit. The gate here is inside effect dispatch, and a recorded decision is re-read on a resume rather than re-asked, which a verdict fetched over the network could not be
ACP as a control planeA permission request is answered from a list of optionIds the agent supplied; there is no amendment or deferral; a pending request dies with the client process; rawInput is arbitrary JSON a field-level gate cannot bind to; and the client’s filesystem and terminal are not a sandbox. Recording such a session is supported → observing an agent you do not run
Per-capability aggregates as a plane queryAn export carries every record, so each effect’s outbound_bytes joins to its run’s capability as a distribution, which a fixed vector of totals cannot give. count_by_outcome and waiting_runs each answer an operator verb; a baseline names nothing anybody does next. What would change this: a figure an operator acts on in the plane that an export cannot supply
The operator verbs on the MCP tool listWho is acting comes from the request’s identity, never from its body, and a tool call is arguments — a projection puts the actor there and retires four-eyes. Every operator route already carries its own api: capability over HTTP
A rate-limit wait the runtime takes by suspendingA suspension inside the retry loop holds a worker and adds an ordered read to every strict cursor, and the peer’s window is not a field of the recorded failure. Retry-After is honoured up to RetryPolicy::max_advice (60 s); a longer wait is a skill’s, via cx.sleep() with a declared window
Proving a policy set rather than testing itQuantifying over context.args needs a declared argument shape per effect kind, which means one Cedar action per kind — a change to the stored action vocabulary. agentplane policy check re-derives recorded verdicts, and a bundle loaded with a schema refuses a rule no request can reach
A curated event type between the journal and the wireRuntime::journal() plus JournalStore::read(run, from) is already a seq-cursored, reconnect-safe stream, and a third vocabulary would drift from both. Live deltas (ModelCall::streaming_to) stay advisory and unjournaled, because a durable delta stream is a second truth beside the terminal Completion

Deferred

Serving ACS as a Guardian — so agents this runtime does not execute can be governed by a plane that journals the decision. The mapping is close to complete: Cedar is the deterministic layer, the sink gate is allow/deny, an amendment is MODIFY, an approval task is ASK, a suspension is DEFER, and the audit chain is already SHA-256 over RFC 8785 canonical JSON — ACS’s own construction with the fields in the other order. Waiting on: the wire. Where such a session’s record goes is settled → observing an agent you do not run.

A measured containment claim. The runtime claims injection containment, not immunity, and no external measurement is attached to it. A static attack set would manufacture exactly the confidence this project refuses. Waiting on: one adaptive, defence-aware evaluation, over A2A, graded from the journal — the methodology exists in published form (2606.26479).

Two things will be published as part of that number rather than under it, because each moves it more than the attack set does: the policy bundle it ran under, since a deterministic gate has no attack-success rate and a deployment does, and how open the tasks were, since an adaptive attacker does far better against a task that leaves the agent latitude about what to do (2606.15057). A containment figure quoted without both is a figure about somebody’s configuration.

🔍 How to check any of this

Nothing on this page is a promise; all of it is checkable.

just anchors    # every mutation still anchors in the code it names
just features   # every optional feature compiles on its own
just audit      # no dependency in the tree has a known advisory
just ci         # lint, feature configurations, examples, docs, packaging
just ci-full    # the above, plus TLA+ specs and the full mutation sweep

just test-a2a-tck   # the protocol project's own conformance kit, on a live socket
just test-postgres  # the shared-store backend against a real server
just test-vault     # the key-ring contract against a real Vault
just test-live      # the model and embedding drivers against real APIs (costs money)

python3 tools/mutants.py <name> --verify   # one guarantee, end to end
MUTANTS_SHARD=2/10 just mutants          # one slice, for a machine that is not alone

The mutation sweep is the one that matters most: it breaks each guarantee on purpose and requires the test written for it to fail. That is why this page asserts no inventory of guarantees — an inventory is a claim a reader has to trust, and a sweep is one they can run. What the sweep reports, and why just anchors is not a substitute for running it, is on assurance.