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
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.
| Surface | Expect | Why |
|---|---|---|
| What the freeze will cover | ||
| Effect / disposition / recovery vocabulary | stable | DidNotHappen/InDoubt/Landed and the recovery classes are the load-bearing idea; changing them would be a different system |
| Journal record format | will change | not 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 schema | additive, with hard cuts | deny_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 change | generated from the routes and the types they answer with, so it moves with them; generate a client per version → the operator surface |
| Error enums | additive; #[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 fields | additive | it 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 context | the trait is settled; context gains attributes as the runtime learns to say more. Guard optional ones with has → security |
| Store traits | stable seam, growing contract | the conformance battery is the contract; it gains cases faster than the traits gain methods |
testkit | stable, additive | it is how embedders test their own stores and skills, so churn here costs more than it saves |
Skill, StepCtx core methods | stable in shape, additive | new capabilities arrive as new methods; existing ones are not expected to change signature |
Runtime admission methods | settled | every 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 / ToolFailure | settled | Tool::call returns ToolFailure, named by disposition rather than transport; references are tool://server/name |
| Effect keys | will change | any 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 writes | will change without migration | there 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 behaviour | tracks the specs | exact 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.
| # | Condition | Where the evidence is |
|---|---|---|
| 1 | Canonicalization is versioned and vector-checked — a rule change must read as unverifiable, never as a divergence | RFC 8785 held to the standard’s own number vectors → canonical JSON |
| 2 | Golden corpora for the journal record format — a fixed set of records every future build must still read and still hash identically | tests/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 |
| 3 | Golden vectors for the export format — the artifact a third party verifies without this crate | tools/verify_export.py, re-deriving all 33 record vectors from the specification alone |
| 4 | A stated unknown-field policy per durable format | per format, not once — a record body, a sealed header and an export’s framing each say what they refuse → format |
| 5 | Upcasters exercised end-to-end, not only unit-tested | both 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 |
| 6 | A migration and rollback procedure, written down and rehearsed | readers 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 |
| 7 | An algorithm-agility plan — how SHA-256 is replaced without invalidating history | written down: hashes agile by version, signatures by key, and nothing rehashes stored bytes |
| 8 | The deferred format questions are settled | none 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 |
| 9 | The surface the promise attaches to is named | the 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 freeze | Terms | |
|---|---|---|
| The journal record format | yes | a shape changes by upcaster and a version bump, never by a hard cut |
| The export format | yes | the artifact a third party verifies, against a published specification a second implementation already reads |
| The operator verbs and their vocabulary | yes | an outcome, a disposition, a recovery class and a run status keep their spelling; a verb gains arguments and does not lose them |
| The manifest schema | additive | an added field is safe by construction, a removed one is a hard failure |
The Rust API — Skill, StepCtx, Runtime, the store traits | no | pre-alpha and additive in intent, pinned by an exact version in practice |
| Store schemas | no, deliberately | the 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
| Refused | What forecloses it |
|---|---|
| Cross-run mutual exclusion over a declared resource | An 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 step | ACS-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 plane | A 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 query | An 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 list | Who 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 suspending | A 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 it | Quantifying 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 wire | Runtime::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 aloneThe 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.