Transactional write gate for coding agents: plan, canonical manifest, checks, two-phase apply.
Docs · Quickstart · Tools · Architecture · Packages · Contributing
An agent describes a change set as a Plan. AXIOM compiles it to a canonical,
content-addressed Manifest, runs checks over the whole set, and applies it with a
hash-gated two-phase commit that leaves a journal and, optionally, a signed attestation.
It ships as one npm package — @codai/axiom-mcp — that is an MCP server, a CLI, a
PreToolUse hook and a GitHub Action.
apply demands the digest you inspected (confirmDigest), re-hashes every pre-image at
commit, and rolls back to the byte-identical prior tree on any failure — on a tree several
agents share.verify --tree proves a tree matches a
manifest and emits an in-toto attestation that CI uploads to Sigstore.flowchart LR
subgraph entry [Entry points]
direction TB
MCP[MCP server<br/>stdio · Streamable HTTP]
CLI[CLI<br/>axiom compile · check · apply]
HOOK[PreToolUse hook<br/>axiom gate --stdin]
GHA[GitHub Action<br/>dragoscv/axiom/action@v2]
end
P[Plan<br/>JSON or .axm] -->|compile| M[Manifest<br/>JCS · sha256 per file<br/>blobs · CAS · ref · patch]
M -->|check| C[CheckReport<br/>pass · fail · error]
C -->|apply · 2PC<br/>confirmDigest| T[Repository tree]
T --> J[Journal · ApplyResult<br/>DSSE signature · in-toto attestation]
J -.->|rollback| T
entry --> P
| Channel | Command | Platforms |
|---|---|---|
| Run without installing | npx -y @codai/axiom-mcp mcp --root . | anywhere with Node ≥ 22.14 |
Global bin (axiom) — required for hooks | npm i -g @codai/axiom-mcp | anywhere with Node ≥ 22.14 |
| Standalone binary, no Node (from 2.2.1) | curl -fsSL https://dragoscv.github.io/axiom/install.sh | sh | linux-x64 · linux-arm64 · darwin-arm64 · darwin-x64 |
| Standalone binary, no Node (from 2.2.1) | irm https://dragoscv.github.io/axiom/install.ps1 | iex | win-x64 |
| Homebrew (same binary) | brew install dragoscv/tap/axiom | macOS · Linux |
VS Code .axm extension | axiom-axm-<version>.vsix on the GitHub release | VS Code ≥ 1.138 |
| GitHub Action | uses: dragoscv/axiom/action@v2 | ubuntu · macos · windows runners |
| MCP Registry | io.github.dragoscv/axiom | any registry-aware MCP client |
Then wire a repository in one idempotent step and check it:
npm i -g @codai/axiom-mcp
axiom init # .vscode/mcp.json + PreToolUse hook (.github/hooks/axiom-gate.json or .claude/settings.json)
# + .axiom/profiles/<repo>.json + .axiom/gate-profile.json + .gitignore lines
axiom doctor # bin on PATH, hooks, gate p95 latency vs the 5 s hook timeout, lock, journals, profiles
init detects the harness (--harness auto|copilot|claude|codex|vscode), merges into existing
JSON instead of overwriting (--force to replace), and writes only a note for Codex, which has no
hook API. doctor exits 2 when a check fails. Per-harness files:
docs/getting-started/install.md.
Binaries ship with SHA256SUMS and Sigstore provenance; npm packages carry npm provenance.
How to check them: SECURITY.md.
1. Point an MCP client at a repo — .vscode/mcp.json (Claude Desktop config is the same
shape, see packages/mcp/README.md):
{
"servers": {
"axiom": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@codai/axiom-mcp", "mcp", "--root", "${workspaceFolder}"]
}
}
}
--root is an explicit allowlist and may repeat; there is no cwd or env fallback.
2. Write a Plan — plan.json:
{
"apiVersion": "axiom.dev/v2",
"kind": "Plan",
"name": "hello",
"intent": "Add a greeting module and document it.",
"artifacts": [
{ "path": "src/hello.ts",
"source": { "type": "inline", "content": "export const hi = () => 'hi';\n" } },
{ "path": "README.md", "op": "overwrite",
"source": { "type": "inline", "content": "# hello\n" } }
],
"checks": [{ "id": "no-secrets", "predicate": "content.noSecrets", "params": {} }]
}
3. Compile → check → apply from the CLI (the MCP tools do the same):
axiom compile plan.json --root . -o bundle.json # → { manifestDigest: "sha256:…" }
axiom check bundle.json --root . # → CheckReport, verdict pass|fail|error
axiom apply bundle.json --root . --dry-run # unified diff, nothing written
axiom apply bundle.json --root . --confirm sha256:… # two-phase commit, journal under .axiom/
axiom rollback sha256:… --root . # reverse-replay that journal entry
Plan fields, sources (inline, cas, ref, patch, template) and the .axm DSL:
docs/reference/plan-format.md · docs/reference/axm-syntax.md.
axiom gate --stdin reads one harness payload, checks containment, path.deny/allow,
content.noSecrets and content.maxBytes on the write target, scans shell commands for write
primitives, and answers allow (exit 0) or deny (exit 2, JSON reason). Fail-closed; ~100 ms end
to end. Claude Code:
{ "hooks": { "PreToolUse": [ { "matcher": "Write|Edit|MultiEdit|NotebookEdit",
"hooks": [ { "type": "command", "command": "axiom gate --stdin", "timeout": 5 } ] } ] } }
Copilot CLI / VS Code wiring, profile file and the latency budget: docs/getting-started/hooks.md.
[!WARNING] Install the global bin for hooks.
npxresolution takes seconds even with a warm cache, the harness times the hook out, and every harness fails open on timeout.
Fail a pull request whose tree does not match the manifest an agent applied, and optionally upload an in-toto attestation:
- uses: dragoscv/axiom/action@v2
with:
bundle: .axiom/manifests/<hex>.json
root: .
attest: true # needs permissions: id-token: write, attestations: write
Scope, --pre mode and how to verify the attestation later: docs/guides/verify-tree.md.
Eighteen MCP tools, each with annotations and an outputSchema; errors are isError results
carrying a code from the closed ERROR_CODES enum — a handler never throws.
| Tool | What it does | Annotations |
|---|---|---|
axiom_plan_validate | Validate a Plan; ERR_* codes with JSON pointers | read-only |
axiom_plan_compile | Plan → ManifestBundle (inline blobs or CAS); writes only under <root>/.axiom/ | act |
axiom_manifest_verify | Recompute the canonical digest, verify every blob and, with a root, the DSSE signatures | read-only |
axiom_check | Run a profile of predicates over a bundle; fails closed; verifies preImage against the tree | read-only |
axiom_check_start | Same as axiom_check, returned immediately as a task (long guard.external suites) | read-only |
axiom_task_get | Poll a task; result once completed, error once failed/cancelled | read-only |
axiom_task_cancel | Abort a working task and kill its guard process trees | act |
axiom_plan_begin | Open a chunked plan session for Plans over the 4 MiB call cap | act |
axiom_plan_add | Append a chunk of artifacts[] to a session | act |
axiom_plan_seal | Compile the assembled Plan through the same path as axiom_plan_compile | act |
axiom_apply_dry_run | Containment + pre-image check + staging + unified diff; no user files touched | read-only |
axiom_apply | Two-phase commit; requires confirmDigest === manifestDigest; single writer via .axiom/lock | destructive |
axiom_rollback | Reverse-replay the journal of an applied manifest, scoped to its paths | destructive |
axiom_manifest_diff | Added / removed / changed artifacts between two manifests | read-only |
axiom_axm_parse | .axm DSL text → Plan with {line, column} diagnostics | read-only |
axiom_roots_list | The allowlisted roots | read-only |
axiom_status | Lock holder, queue, in-flight intents, interrupted journals, last apply, journal-chain health | read-only |
axiom_repo_snapshot | Deterministic, content-addressed inventory of a root (snapshotDigest) | read-only |
Inputs, outputs, resources (axiom://…), transports (--wire 2026|2025) and the error
contract: docs/reference/mcp-tools.md. CLI verbs (init, doctor, status, log, sign, trust, gc, migrate v1,
snapshot, …): packages/mcp/README.md.
flowchart TB
schema["@codai/axiom-schema<br/>Zod v4 · ERROR_CODES · JSON Schema"]
canon["@codai/axiom-canon<br/>JCS · sha256 · in-toto · DSSE"]
plan["@codai/axiom-plan<br/>compile · CAS · ref · patch"]
checks["@codai/axiom-checks<br/>18 predicates · profiles · guards"]
apply["@codai/axiom-apply<br/>containment · 2PC · journal · verify-tree"]
axm["@codai/axiom-axm<br/>.axm parser"]
lsp["@codai/axiom-axm-lsp<br/>language server"]
web["@codai/axiom-emitters-web<br/>template emitter"]
mcp["@codai/axiom-mcp<br/>MCP server · CLI · gate"]
plan --> schema & canon
checks --> schema & canon
apply --> schema & canon
axm --> schema
lsp --> axm & schema
mcp --> plan & checks & apply & axm & web & canon & schema
schema and canon are leaves; plan, checks, apply depend only on those two; axm on
schema; axm-lsp on axm + schema; emitters-web has no workspace deps (the emitter
registry is an interface injected into compilePlan); mcp depends on everything except the
private testkit; nobody depends on mcp. Enforced by check-package-deps.
Invariants (never weakened; PLAN.md §2, design):
ManifestBody is JCS-canonical; manifestDigest = sha256(JCS(body)); nothing hashed contains a timestamp.blobs (≤ 256 KiB each, ≤ 4 MiB bundle), CAS (.axiom/cas/sha256/…) or a digest-pinned ref.apply requires confirmDigest === manifestDigest; pre-images are re-verified at commit; .axiom/lock = single writer per root.code, never on message text.warn.shell: true.verdict: "error" — fail closed.--root); no cwd fallback.packages/mcp/src/adapter.ts) and lives in lazy chunks.| Package | What | npm |
|---|---|---|
@codai/axiom-schema | Zod v4 schemas for Plan, Manifest, CheckReport, ApplyResult, Profile, Journal, RepoSnapshot; closed ERROR_CODES; JSON Schema export | |
@codai/axiom-canon | JCS (RFC 8785), sha256, in-toto Statement v1, DSSE Ed25519 envelopes | |
@codai/axiom-plan | Plan → ManifestBundle compiler; inline / CAS / ref / patch / template sources; verifyBundle, diffManifests | |
@codai/axiom-checks | 18 predicates incl. expr.cel, expr.cedar, guard.external, manifest.requireSigned; profiles default / strict / permissive | |
@codai/axiom-apply | Containment, staging, two-phase commit, journal, rollback, lock, dry-run diff, PR mode, verifyTree | |
@codai/axiom-axm | .axm DSL → Plan (Chevrotain) with positioned diagnostics; formatAxm | |
@codai/axiom-axm-lsp | Language server for .axm: diagnostics, completion, hover, symbols, formatting, semantic tokens | |
@codai/axiom-emitters-web | Optional template sources — the web@2.0.0 emitter (Next 16, Hono 4, Drizzle, Biome, Tailwind v4) | |
@codai/axiom-mcp | The published bin — MCP server (stdio + Streamable HTTP), CLI, gate --stdin hook, standalone-binary entry |
Private, not published: testkit (golden fixtures, arbitraries), conformance (MCP
conformance harness), vscode-axm (the .vsix). All nine public packages share one version
(fixed Changesets group) and are released together.
A Profile is a list of typed predicates. Built-ins: path.allow / path.deny /
path.reservedNames, content.noSecrets / content.maxBytes / content.encodingUtf8,
manifest.maxArtifacts / manifest.maxTotalBytes / manifest.requireSigned / manifest.noDeletes,
deps.max / deps.deny, repo.noOverwriteOf / repo.requireCompanion, guard.external
(your own scripts/check-*.mjs), expr.cel and expr.cedar (offline policy languages).
Verdict is pass | fail | error; anything that cannot be evaluated is error, which blocks
apply. Catalogue, params and profile authoring: docs/guides/checks.md.
axiom keygen → axiom trust add → axiom sign puts a detached DSSE envelope (Ed25519 over
JCS(manifest)) beside the bundle without changing its digest; a profile with
manifest.requireSigned then refuses unsigned, tampered, untrusted or replayed
(antiRollback) bundles. axiom verify --tree [--attest] emits an in-toto Statement
(https://axiom.dev/attestation/apply/v1). Envelope, key ceremony, root binding and limits:
docs/guides/signing.md · docs/guides/verify-tree.md.
2.3.x shipped. Plan compiler with every source type, 18 predicates, fail-closed apply with
journal/rollback/PR mode, MCP SDK v2 (2026-07-28 wire, --wire 2025 fallback) over stdio and
HTTP, fail-closed gate, .axm DSL + LSP + VS Code extension, DSSE signing,
verify --tree + attestation + GitHub Action, CI on ubuntu/windows/macos, 18 repo guards.
2.2.1 adds standalone binaries with provenance, the MCP Registry listing, the docs site and
the action@v2 tag. 2.4.0 (Phase 7, D-34) adds axiom init / axiom doctor, multi-agent
roots (early ERR_CONFLICT, FIFO lock queue, --lock-timeout), PR mode in an isolated worktree,
Sigstore keyless signing with an issuer/subject policy, axiom status / axiom log /
verify --journal over a hash-chained journal, --keep-backups, YAML Plans — and the 18th MCP tool,
axiom_status. codai's SWE harness routes every write through the gate by default
(docs/integration/codai.md); brivio and metu wirings are in
docs/integration/.
Decisions (D-xx) and stories (S-xxx): PLAN.md · TRACKER.csv ·
MIGRATION.md for 1.x users · docs/reference/versioning.md.
pnpm 12 · Node ≥ 22.14 · TypeScript 7 (tsgo) · tsdown · Biome · Vitest 5 · fast-check · Changesets.
pnpm install --frozen-lockfile
pnpm lint # biome check .
pnpm build # tsdown every package (before typecheck — exports point at dist/)
pnpm typecheck
pnpm test # vitest run
pnpm guards # node scripts/run-guards.mjs — 18 repo invariants
All five green with output shown, a .changeset/*.md for anything under packages/*/src, and
the ripple closed (tool → docs/reference/mcp-tools.md + packages/mcp/README.md + spec/tools.json;
schema → regenerated schemas/*.json; golden → re-pinned). Details: CONTRIBUTING.md
and .github/instructions/.
Discussions for questions · Issues for bugs and features · SUPPORT.md · SECURITY.md (private reporting) · CODE_OF_CONDUCT.md · CITATION.cff.
MIT © Dragos Catalin Vladulescu.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @codai/axiom-mcpMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-dragoscv-axiom": {
"command": "npx",
"args": [
"-y",
"@codai/axiom-mcp"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceAXIOM works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.