Skip to content

Loading a cartridge

How a host boots an .acx cartridge end to end — open and brand-check the file, verify the ROM signature, negotiate the four required tool-roles, load skills, ingest transferable memory, learn field-specific memory into the SAVE zone, and later strip back to a clean, re-shareable ROM.

A cartridge is a self-contained, signed harness — the "agent-OS image" any host boots. In Lilian Weng's framing, a harness is "the system surrounding a base model that orchestrates execution and decides how the model thinks and plans, calls tools and acts, perceives and manages context, stores artifacts, and evaluates results" (Harness Engineering for Self-Improvement, 2026-07-04). Loading is the moment a host takes that portable harness and wires it onto local tools and a local codebase.

What runs today vs. what is specified

The reference implementation is zero-dependency (Node ≥ 22.5.0 node:sqlite + node:crypto). Everything in the open → verify → spec → inspect → strip path below runs verbatim with node --experimental-sqlite. The capability-negotiation handshake, the loop-policy evaluator, live namespace-proof checks, and vec0 vector indexing are specified normatively (SPEC §8, §9, §4.3, §3.5) but are host-side and not exercised by the reference tool — the reference stores vectors in a plain table and re-indexes from the JSON baseline. Where a step is host-side, it is called out.

The loading sequence

sequenceDiagram
    autonumber
    participant H as Host runtime
    participant F as file.acx (SQLite)
    participant R as Trust registry (public keys)
    participant M as MCP tools
    participant C as Local codebase

    H->>F: open() + read header
    Note over H,F: application_id 1094932529 ("ACX1" @68)<br/>user_version 16777472 @60
    H->>F: rebuild ROM manifest from live bytes
    H->>R: look up keyid
    R-->>H: publicKeyPem (or miss)
    H->>H: evaluateTrust() → local/trusted/portable/legacy/tampered
    alt trust == tampered / invalid
        H--xF: refuse — do NOT activate ROM, do NOT touch SAVE
    else trust ok
        H->>F: validate closed package spec + every SQLAR path
        H->>F: read rom/manifest/harness-requirements.json
        H->>M: MCP initialize + bind acx:execute/dispatch/memory.write/search
        M-->>H: compliance descriptor (accept | refuse)
        H->>F: extract rom/skills/<name>/SKILL.md (acx_skill)
        H->>F: read TRANSFERABLE memory (zone=rom) → re-index vectors locally
        H->>C: attach codebase → derive codebaseFingerprint
        H->>F: append FIELD-LEARNED records (zone=save, portable=false)
        Note over H,F: ROM manifest hash unchanged by SAVE writes
        H->>F: strip-to-ROM re-export → hash-equal, clean to re-share
    end

1. Open and brand-check the file

An .acx file is a single SQLite database (SPEC §3.1). Before trusting anything, confirm the two header words a real cartridge sets. file(1) reads them from a 72-byte header range without a page cache:

$ file /tmp/demo.acx
/tmp/demo.acx: SQLite 3.x database, application id 1094932529, user version 16777472, last written using SQLite version 3053002, file counter 14, database pages 33, cookie 0xa, schema 4, UTF-8, version-valid-for 14
Header word Offset Value Meaning
application_id 68 1094932529 = 0x41435831 = ASCII A C X 1 Brands the file as a cartridge; < 2^31 so PRAGMA application_id accepts it.
user_version 60 16777472 = 0x01000100 Packed [MAJOR=1][MINOR=0][vec0_format=1][flags].

Cartridge.open() rejects any file whose application_id is wrong (test: "§12.1 Cartridge.open rejects a non-.acx file (wrong application_id)"). The vec0_format byte matters at import: an importer whose embedding engine differs from the packed format MUST drop and re-index the vectors table (SPEC §3.1, §7.6) — see step 6.

2. Verify the ROM signature (evaluateTrust)

The whole-file SHA-256 is deliberately not signed — SQLite mutates the change counter (offset 24), freelist pages, and page layout on every write, so any SAVE-zone write would break a file-level hash while the ROM is untouched (SPEC §3.3). Instead the host recomputes the ROM integrity manifest from live bytes — every objects row with zone='rom', sorted by (kind, source_ref), canonicalized with RFC 8785 (JCS), hashed — and checks the DSSE / in-toto envelope over it (see Signing & trust).

evaluateTrust() (SPEC §4.5) returns one of five verdicts, evaluated in order:

Trust Condition
tampered DSSE fails, a recomputed ROM object digest ≠ its manifest entry, or subject.digest.sha256 ≠ manifest_hash. Refuse.
legacy No DSSE envelope; a bare pre-standard signature.json verifies. Import with warning.
portable DSSE verifies, but keyid absent from the registry (or namespace-proof unverified / key downgraded). Import with warning.
trusted DSSE verifies AND keyid is active, namespace-proof valid, not expired/revoked.
local As trusted, and keyid equals the verifying host's own key.

The trust registry carries public keys only — private key material must never appear in a cartridge or registry (loadTrustRegistry refuses private key material, SPEC §4.4).

$ acx verify /tmp/demo.acx
status:   warning
trust:    portable
summary:  Signature valid but signer not in trust registry.
keyid:    ed25519:17bb8c9290fd2a3d0c3a434ad0e99544d809dbff1540d64be0bab2274df14f66
signedAt: 2026-04-03T13:35:46.190Z
issues:   Signer keyid not in trust registry
exit=0

The signature is cryptographically valid — the host just doesn't know the signer yet, so it degrades gracefully to portable (exit 0, importable with a warning).

verify (empty registry):            warning / portable - Signature valid but signer not in trust registry.
verify (trusted registry, local key): verified / local  - Signed by this instance.
verify (objects.oid tamper):             invalid / tampered - ROM content diverges from signed manifest (object hash mismatch).
verify (SKILL.md content tamper, oid stale): invalid / tampered - ROM content diverges from signed manifest (object hash mismatch).

Editing a signed sqlar blob while leaving a stale objects.oid is caught because the host recomputes each oid from the live bytes, never from the self-declared value.

A tampered/invalid verdict is a hard stop

acx verify exits non-zero on invalid/tampered. On refusal the host MUST NOT activate the ROM and MUST NOT mutate the SAVE zone (SPEC §8.5). keyid itself is only a registry-lookup hint — verification never depends on it beyond the lookup (SPEC §4.2).

3. Validate and inspect what you're about to boot

Treat acx spec as an activation gate, not just a pretty-printer:

acx spec /tmp/demo.acx
acx inspect /tmp/demo.acx

acx.package-spec/1 is closed and exact. It must contain one each of the eight roles identity, memory-baseline, memory-vectors, skills, capabilities, harness, loop-context, and level, with no unknown, duplicate, missing, or extended entries. Its cartridge id, spec version, and embedding engine must match ROM-bound metadata exactly. The same gate scans every live SQLAR name: only relative portable ASCII paths rooted at rom/ or save/ are accepted; absolute paths, empty/./.. segments, backslashes, NUL, and traversal forms fail before extraction.

acx inspect is the host's read-only pre-flight over meta, ROM object counts, skills, capabilities, memory zones, and attestations:

$ acx inspect /tmp/demo.acx
== meta ==
  acx.agent_name = Scenario Research Designer
  acx.cartridge_id = io.github.agentibus/scenario-research-designer@025edd67-cc60-47b8-a059-ddd839c29db5
  acx.created_at = 2026-04-03T13:35:46.190Z
  acx.declared_level = 4
  acx.embedding_engine = {"id":"local-hash-128","dim":128}
  acx.model = gemini-2.5-pro
  acx.provider = gemini
  acx.publisher_id = io.github.agentibus
  acx.role = designer
  acx.rom_manifest_hash = sha256:f479be021b8ea2e55cc6e3e33b95df9d151196548dfc854dedbe578be7120642
  acx.spec_version = 0.1
  acx.vec0_format = 1

== ROM objects ==
  total: 21  (memory:1, cartridge:9, sqlar:11)

== skills (acx_skill) ==
  - expertise-designer: Specialized designer expertise on research, ux, benchmarking. Use when a task matches this agent's d

== capabilities ==
  - implement-feature[pkg:generic/benchmarking+pkg:generic/research+pkg:generic/ux]  verified=false
  - build-dag[pkg:generic/snowflake+pkg:pypi/apache-airflow+pkg:pypi/dbt-core]  verified=false

== memory (by zone) ==
  rom: 1

== attestations ==
  (none)

declared_level is a claim, not a proof

acx.declared_level = 4 and verified=false are the publisher's self-assertions. A capability only reads verified=true once an independent provable-level attestation resolves against the ROM digest. Treat unverified capabilities as claims until then.

Note acx.embedding_engine = {"id":"local-hash-128","dim":128}: the ROM manifest declares float[384] as a template, but this cartridge's real vectors are dim-128 — the host materializes the concrete dimension from embedding_engine.dim (SPEC §3.5).

4. Read harness-requirements and negotiate tools

Every cartridge embeds exactly one harness-requirements manifest at sqlar path rom/manifest/harness-requirements.json (schemaVersion: "acx.harness.v1", ROM-zone, covered by the signature). A host MUST refuse activation if it fails verification or its schemaVersion is unrecognized (SPEC §8.1). ROM content invokes tools by role, never by a host-specific tool name — this is what makes a cartridge portable across hosts.

The required minimal contract is exactly four roles; absence of any one MUST cause refusal (SPEC §8.4):

Role Scopes What it does
acx:execute exec (+ optional fs.read) Run commands/code in a sandbox; returns the MCP content:[{type:"text",text}] envelope.
acx:dispatch dispatch Spawn/route a sub-agent unit of work.
acx:memory.write memory.write Append a durable SAVE-zone record (ref tool agentibus_add_memory).
acx:search search, memory.read Retrieval over memory + knowledge (ref tool agentibus_search_memory).

Read-only inventory tools (agentibus_list_agents, agentibus_project_status, …) are tier:"optional" — their absence must not block activation, and the cartridge runs degraded when they're unbound.

The handshake (SPEC §8.5, host-side): the host (1) signature-verifies the manifest, (2) runs MCP initialize, (3) binds each role to a concrete tool whose advertised inputSchema is structurally accepting of the contract schema and whose granted scopes ⊇ the role's capabilityScopes, then (4) emits a compliance descriptor (application/vnd.acx.harness-compliance.v1+json) carrying a requirementsHash, bindings[], and a verdict of accept or refuse. It refuses when the negotiated protocolRevision < mcp.minProtocolRevision (floor 2024-11-05), a required role is unbound, a scope is denied, model.toolUse is required-but-false, or contextWindowTokens < minContextWindowTokens. On refuse it returns MCP's exact JSON-RPC error: code -32602, message "Unsupported harness".

Specified, host-side — not in the reference tool

The negotiation runtime and the MCP initialize handshake are normative but not implemented in the zero-dependency reference impl. The reference verifies only that the embedded manifest matches its schema (test: "§8 harness-requirements manifest matches its schema (requiredTools, no forbidden keys)"). See Harness requirements for the manifest shape and Loop & context policy for the loop the host then drives.

5. Load skills

Skills travel wholly in the ROM zone as stock SQLite Archive (sqlar) rows under rom/skills/<name>/SKILL.md, with agentskills.io frontmatter. Because sqlar keeps the exact stock schema, they extract with off-the-shelf sqlite3 — no ACX code required:

$ sqlite3 /tmp/demo.acx -Ax
rom/skills/expertise-designer/SKILL.md

The acx_skill table is a derived index (SPEC §5.3) that lets a host enumerate skills without unpacking; its content_sha256 matches the extracted bytes (test: "§12.7 sqlar skills are extractable byte-for-byte and index content_sha256 matches"). The authoritative content is always the SKILL.md in sqlar. See Skills.

6. Ingest TRANSFERABLE memory (re-index vectors locally)

Memory is a two-tier partition (SPEC §7). Every record carries a mandatory boolean portable and a codebaseFingerprint:

  • TRANSFERABLE (portable: true) — codebase-agnostic, ROM zone, codebaseFingerprint and repoId MUST be null. These are the only records eligible to merge into the host's native memory.
  • FIELD-LEARNED (portable: false) — codebase-specific, SAVE zone, MUST carry a non-null codebaseFingerprint. Written in step 8.

The memory-records.json canonical-JSON projection is the always-present portable baseline and the sole source of truth on import (SPEC §7.6). Vectors are derived, never signed, and engine-specific — so on import the host MUST re-index every record from the JSON baseline using its own engine and MUST discard foreign vectors. A bundle with vectors but no baseline is invalid.

Import merge is idempotent and reuses AGENTIBUS' two-key merge unchanged: dedupe by id, then by the 10-hex-char artifactFingerprint; conflicts resolve deterministically (longer text wins, worse impact wins, max xpAwarded, union tags, latest timestamp). portable and codebaseFingerprint are excluded from the fingerprint, so a record's content address is tier-independent — merging is commutative and idempotent (SPEC §7.3), and re-tiering never forks a duplicate.

vec0 is templated, not run, in the reference impl

The DDL declares a vectors vec0 virtual table, but the reference implementation uses a plain table and re-indexes from the JSON baseline. Real vec0 indexing is host-side (SPEC §3.5, §7.6).

7. Start a codebase — derive a fingerprint

To attach the harness to a real repository, the host derives a privacy-preserving codebase fingerprint (SPEC §7.2):

codebaseFingerprint = "cbf1_" + HMAC-SHA-256(key = installationSalt, msg = canonicalRepoIdentity)   [first 40 hex chars]

canonicalRepoIdentity is the git origin URL normalized to host + "/" + path (lowercased, credentials removed, default port removed, trailing .git stripped), falling back to the root-commit SHA when there's no remote. The installationSalt is a ≥ 256-bit org-scoped secret held outside the bundle and never exported. The fingerprint MUST NOT contain or derive from the repo name or label. The secret salt makes it dictionary-resistant; org-scoping makes it intentionally non-correlatable across orgs — that is the quarantine boundary.

8. Learn into the SAVE zone

As the agent works, acx:memory.write appends FIELD-LEARNED records (portable: false, zone: 'save') tagged with the current codebaseFingerprint. These are quarantined by default: excluded from re-share unless --include-field-learned is passed, and even then they MUST NOT re-project onto an importer's codebase (SPEC §7.4).

Crucially, SAVE writes never touch the ROM. Field-learned records are quarantined by default so no repoId leaks into ROM memory (test: "field-learned records are quarantined by default"), and the on-disk file digest legitimately drifts while the signed ROM manifest stays identical. This mirrors Weng's rule that a harness "should keep durable state in files" rather than in context — the cartridge is that durable, on-disk state.

flowchart LR
    ROM["ROM zone<br/>signed · immutable · shareable<br/>skills · capabilities · policy · portable memory"]
    SAVE["SAVE zone<br/>mutable · field-learned<br/>codebase-specific memory"]
    ROM -. "manifest hash unchanged" .- SAVE
    SAVE -->|strip-to-ROM| CLEAN["clean re-shareable ROM<br/>hash-equal to original"]

9. Strip-to-ROM to re-share cleanly

When you want to hand the cartridge on — publish it, share it directly, or push it to a registry — strip-to-ROM removes every SAVE-zone row and re-exports. The recomputed ROM manifest hash MUST equal the original signed hash; that equality is the machine-checkable proof the ROM was never mutated by field learning (SPEC §3.4):

$ acx strip /tmp/demo.acx /tmp/demo.rom.acx
rom hash before strip: sha256:f479be021b8ea2e55cc6e3e33b95df9d151196548dfc854dedbe578be7120642
rom hash after  strip: sha256:f479be021b8ea2e55cc6e3e33b95df9d151196548dfc854dedbe578be7120642
hash-equality proof:   EQUAL (ROM intact; SAVE removed)
wrote:                 /tmp/demo.rom.acx
exit=0

Under the hood: DELETE FROM memory WHERE zone='save' (and the matching sqlar, vectors, objects rows), clear acx.save_codebase_fingerprint, clear the SAVE flag bit, VACUUM. The existing signatures row re-verifies unchanged. A fail-closed scrub gate also runs on export before signing — it blocks (non-zero exit) on any AWS key, PEM private key, GitHub token, JWT, or URI credential; local Unix, Windows, and ~/… home paths are flagged for normalization, and secrets are never silently redacted (SPEC §7.5; tests: "scrub gate FAILS CLOSED").

The stripped, re-signed ROM is now ready for distribution as an OCI layer — the last lifecycle step.