Skip to content

Learned bundles (`marina.learned.v1`)

Experimental. Learned bundles (learned, bun run learned) are experimental: built and tested, but no production deployment uses them yet, and their interfaces may change. See feature status.

A learned bundle carries what one Marina learned to another Marina: the lessons its outcomes taught, the defaults it earned, aggregate evidence from its benchmark ledger, the roles it adopted and the conventions it ratified. The bundle is signed by a publisher. Before anything is imported, the bundle is checked against publisher keys that the operator pinned.

Imported knowledge is never trusted on arrival. Lessons arrive with the trust level imported. Agents are served them, labelled imported, below anything this Marina learned itself, until its own outcomes confirm or contradict them.

Nothing changes for an install that never exports or imports a bundle. With MARINA_UPSTREAM unset, the only bundles that import are open bundles signed by a pinned publisher key. No key is pinned by default, so nothing imports until an operator pins one or sets MARINA_UPSTREAM=on.

A bundle is a directory. Every file except signature.json is listed with its sha256 hash in manifest.json, and signature.json signs the manifest (Ed25519, through src/net/federation-crypto.ts).

File Contents
manifest.json The bundle’s identity, version and lineage, publisher, licence and terms, access fields, slices, item counts and the hash of every file.
signature.json The publisher’s Ed25519 signature over the canonical manifest.
spec.json The spec sheet: counts by kind, domain, tier and source trust; the rank distribution; judge labels; confirmation rates; evidence summaries for defaults; scan counts; tier sizes.
diff.jsonl One line per item_key whose status is added, changed, re-ranked or retired, compared with the parent version.
lessons.jsonl Lessons and meta-lessons that the publisher served as trusted.
conventions.jsonl Records ratified into institutional spaces (guide, orchestration:*, tradition:*).
defaults.jsonl Promoted benchmark defaults, each with an evidence summary that holds numbers only.
evidence.jsonl Aggregate ledger statistics per descriptor, family and (internal packs only) benchmark. A cell is exported only when it has at least 20 items.
roles.jsonl Roles adopted through world adopt, as RoleBundle v1.
world.jsonl World bundles only: the data-only world document (rooms, exits, guide notes). Older bundles that carry quests still import; the quests are ignored.
room-sources.jsonl World bundles only: room source code as inert text, flagged for review under world.code.

Every item has two identifiers:

  • an item_key: a stable, opaque identity that survives edits across versions;
  • a content_hash: the hash of this version’s content, excluding rank, tier and provenance.

Each record also carries a provenance chain. Every hop names its producer, an opaque origin, hashed outcome references, the judge and whether it is calibrated, and a role label such as ratifier. A hop never names a person.

The manifest’s excludes field lists what the bundle never carries: identity, accounts, standing, competence, gate_grants, ledger_rows, approvals, holds, secrets, runtime_journals, continuity_archives and numeric_note_ids. Verification refuses a bundle whose contents overreach its manifest. It refuses in four cases:

  • the manifest lists excludes but leaves one of these entries out;
  • the bundle has a file the format does not define;
  • a kind has more items than the manifest declares, or the allow-list names an unknown class;
  • an item has a field that belongs to an excluded layer, at any depth. Examples are standing, user_id, api_key, transcript and note_id.

Bundles written before this field existed have no excludes field, and they still verify and import. The other three checks apply to them too.

The manifest identifies the artifact with these fields:

  • artifact_id: marina-memory:<publisher key fingerprint>/<name>, or marina-world:… for a world bundle;
  • version: semver;
  • generation: a monotonic integer, used to refuse downgrades;
  • parent: {version, generation, manifest_digest};
  • lineage: every ancestor, oldest first.

A world’s state does not have to live in one long-lived database. A world can be the code seed plus the bundles you import into it. To move an old world over, distill its database into a bundle, archive the database, and start a new world. Distill and archive:

Terminal window
marina learned distill ./old/marina.db --out ./packs/old-world --archive

Start a new world from the code seed:

Terminal window
marina start

Then import the bundle into it:

Terminal window
MARINA_UPSTREAM=on DB_PATH=marina.db marina learned import ./packs/old-world --own --allow-unsigned

marina learned distill <db> opens the database read-only. It takes a consistent snapshot with SQLite’s VACUUM INTO, which works even while a server holds the file. It then runs the ordinary export allow-list over the snapshot and removes the snapshot. Nothing is written to the source database.

  • The bundle carries trusted lessons, ratified conventions, promoted defaults, ledger aggregates and adopted roles, and every item is scanned. Everything else stays behind: accounts, standing, gate grants, approvals and holds, secrets, journals and numeric note ids. The manifest’s excludes field lists these.

  • --out defaults to <db>.learned. --name defaults to distilled. --publisher defaults to MARINA_NAME, else local.

  • With MARINA_LEARNED_SIGNING_KEY or --key-file, the bundle is signed like any export. Without a key it is written unsigned:

    • it has no signature.json;
    • its publisher key is unsigned;
    • its artifact id starts marina-memory:unsigned-.

    An unsigned bundle verifies and imports only with --allow-unsigned, and every record it writes is labelled unsigned. A signed bundle still needs its publisher key pinned.

  • No model or provider is needed.

--archive, or marina learned archive <db> on its own, moves the database and its -wal/-shm/-journal files into <db>.archive-<UTC time>/, next to the database. Nothing is deleted. Archiving is refused while a server holds the database’s lease (<db>.instance-lock), so stop that server first.

Import at boot. MARINA_BOOTSTRAP_BUNDLES=<dir>[,<dir>] names bundles that the server imports right after the world seed and before any agent wakes. A directory without a manifest.json stands for every bundle directly inside it, in name order.

  • Naming a bundle here is the opt-in for that bundle, whatever MARINA_UPSTREAM says. Every other import rule applies: pinned signatures, revocations, no downgrade, trust imported, and seeds only for empty slots.
  • MARINA_BOOTSTRAP_ACCEPT widens what is accepted:
    • own covers your own private slices (never a paid token slice);
    • unsigned accepts a bundle with no publisher key.
  • A bundle already imported at the same generation and digest is skipped, so a restart writes nothing.
  • A refused bundle is logged and audited, and the world boots without it.

With the variable unset, boot is unchanged.

Terminal window
MARINA_BOOTSTRAP_BUNDLES=./packs/old-world MARINA_BOOTSTRAP_ACCEPT=own,unsigned marina start

Imported knowledge starts as imported, and your new world earns trust again through its own verified work. Standing and gate competence are never carried, because they belong to accounts that were verified by other accounts.

Export is an operator script and never an in-world command:

Terminal window
DB_PATH=marina.db MARINA_LEARNED_SIGNING_KEY=$(cat ~/.marina/learned.key) \
bun run learned export --out ./packs/curated-1.0.0 --name curated \
--publisher "Example Org" [--profile internal|public] [--parent ./packs/curated-0.9.0]

Allow-list only. Export reads exactly these sources and nothing else:

Class Source
Lessons Current records with trust trusted in the lessons:* spaces, meta-lessons included. Unverified, rejected and retired lessons are never exported.
Conventions Current records in institutional spaces that carry ratified_by.
Defaults benchmark_defaults rows whose incumbent run is still valid.
Evidence Completed ledger runs, aggregated per cell. A cell is cut when it has fewer than 20 items. Run ids, item ids and answer hashes are never exported.
Roles Roles whose world adopt request was applied.

These are never exported:

  • private memory, process memory or core memory, and memory kv;
  • pool notes;
  • transcripts and traces;
  • item-level outcomes and answer hashes;
  • human names and secrets.

Scans: an item is dropped, never redacted. Every candidate item is scanned. An item that fails a scan is dropped from the bundle, and the export prints its key, kind and reason but never its content.

Scan What fails it
secret Key-shaped strings, private-key blocks, bearer tokens, password=-style assignments and JWTs. Also the value of any of this process’s own environment variables whose name ends in KEY, SECRET, TOKEN or PASSWORD.
instance Emails, IPv4 addresses, absolute paths, internal host names, UUIDs, URLs with a query string or credentials, and phone-like numbers. Also the host name, MARINA_NAME and any --instance-token.
human-name Any local account name of 4 or more characters, matched as a whole word.
benchmark-text Any 6-word shingle, or 40-character window starting at a word, shared with a local benchmark dataset. The default dataset is benchmarks/datasets; add more with --bench-corpus <path>.

References are salted with the publisher key id and hashed. For example, bench:<run id> becomes bench:<hash>.

Profiles. internal packs are for backups and moves between your own worlds. public packs differ in two ways:

  • arena lessons are held back;
  • evidence is reported at family level only. A benchmark joins a family only through --family <name>=<bench>,<bench>, and a benchmark with no declared family is dropped.

Versions. With --parent <dir>, export does four things:

  • checks that the parent was signed by the same key and carries the same name;
  • sets generation to the parent’s plus one;
  • records the parent and the lineage;
  • writes diff.jsonl.

The version is bumped automatically:

Change since the parent Bump
The profile changed Major
Items were added Minor
Anything else Patch

An explicit --version must be greater than the parent’s version.

Tiers. Every item is placed in one tier:

Tier Contents
core At most 8 KB of rendered text: the defaults, then the highest-ranked lessons and conventions. Sized for small models.
standard Adds the remaining lessons and conventions, plus the evidence aggregates.
full Adds the roles.
Terminal window
bun run learned verify ./packs/curated-1.1.0 [--revocations revocations.json]
bun run learned diff ./packs/curated-1.0.0 ./packs/curated-1.1.0
MARINA_UPSTREAM=on DB_PATH=marina.db bun run learned import ./packs/curated-1.1.0 --own

--own imports your own private pack, which is the default export. Open slices need no flag, and paid slices need --entitlement; see Entitlements.

Verification refuses the bundle at the first failure in this order:

  1. the schema is not marina.learned.v1;
  2. the signature does not verify against a pinned key, or the signing key is not the manifest’s publisher key. An unsigned bundle passes this step only with --allow-unsigned;
  3. a file’s sha256 does not match the manifest, or an item file is present that the manifest does not list;
  4. an item does not parse, is in the wrong file, or has a content_hash that does not recompute;
  5. the contents overreach the manifest (see excludes above).

A refused bundle is never imported through any fallback. Import additionally refuses three cases:

  • a generation older than one already imported (a downgrade);
  • a generation already imported with different content;
  • a version that its publisher revoked.

Import also runs the export scans again.

Class Where it lands
Lessons upstream:lessons:<domain> spaces owned by the marina:upstream account. Each lesson has trust imported, its original resolved_at (so the leakage rule applies unchanged), its serving scope (scope, families, scope_family) and the publisher’s score as publisher_rank. The boards it names become eval:<board> refs. Imported lessons are never written into lessons:*; they are served from their own spaces, labelled imported (see Serving imported lessons).
Conventions The upstream:conventions space, with trust imported.
Defaults upstream_default_seeds, and only for a slot that has no local benchmark_defaults row. resolveDefault reads the database’s active seeds below every local layer (upstreamSeedFor in src/learned/upstream-seed.ts) and traces the answer as source upstream. A seed answers nothing once a local default exists for its slot, and the operator’s env value always wins. A seed for a slot that chooses a model (every slot class except memory-ranking: role-model:*, verify:checker, forecast-config*, crew slots and run-target slots) is withheld under MARINA_UPSTREAM_MODEL_SEEDS=propose, the default. Its trace marks it withheld, and each slot and value raises one ops inbox approval. A person holding role.edit adopts it with benchmark promote <slot> upstream (or the model id, for a role-model slot), recorded with basis upstream and the bundle’s artifact, version, item and value digest. An agent never adopts one. serve serves those seeds directly.
Evidence evidence_priors, down-weighted (weight 0.25, at most 50 items per cell). Evidence never enters benchmark_runs or benchmark_items.
Roles Created under the upstream. prefix, for both the role and its traits. A role is only ever created, never bound to an agent, and changing it later takes role.edit.

Importing a newer generation changes local state in three ways:

  • an item it no longer carries is retired locally;
  • a changed item replaces the earlier version, and the earlier version stays readable;
  • an unchanged item is left alone.

Every action is written to the append-only upstream_events table, including refused imports, dropped items, skipped slots, seeds, priors and roles.

Confirmation. An imported lesson never becomes trusted by being imported. Only this Marina’s own judged outcome loop confirms it. To do so, the loop writes a local, trusted lesson that cites the imported lesson as upstream:<item_key>. confirmImportedLesson (src/learned/import.ts) checks that citation, then:

  • records the confirming lesson in learned_items.confirmed_by;
  • adds confirmed_locally_by to the imported record’s metadata.

The imported record itself keeps trust imported. The trusted knowledge is the local lesson that confirmed it, and recall serves that lesson instead.

When imports are allowed. MARINA_UPSTREAM has three settings:

Value What imports
on Any bundle that verifies, subject to the access rules below.
unset (default) Only a bundle signed by a pinned publisher key whose access and every slice are open. An unsigned bundle, a private or token slice, an entitlement or --own never helps. Anything else is refused as off refuses it, with nothing written. The built-in pinned key list is empty, so this imports nothing until a key is pinned.
off Nothing, except bundles named by MARINA_BOOTSTRAP_BUNDLES.

Imported lessons reach agents through the same lesson recall as the world’s own lessons: the resident [Lessons] section, lessons <topic>, forecasts, Code Mode tasks and opted-in passthru requests. MARINA_IMPORTED_LESSONS controls it:

Value Effect
unset (default) on once anything has been imported.
on Imported lessons are recalled beside local ones.
observe What would be served is recorded (upstream_events action observed), and nothing is served.
off Imported lessons are never read. Every surface is exactly as without imports.

Served imported lessons follow these rules:

  • Labels. Each is shown as <lesson> (imported), or (imported, unsigned) when its bundle was unsigned.

  • Rank. Each is ranked after every local trusted lesson and every curator’s lesson. Rejected and unjudged lessons are never served.

  • Scope. Family scoping, leakage rule 2 and the as-of fence apply exactly as for local lessons:

    • a lesson scoped to a family reaches only work of that family;
    • a lesson tagged for a board stays out of that board’s measurements;
    • under an evidence cutoff a lesson is visible only after its resolved_at, and an undated lesson is never visible.
  • Owner scope. A person’s own lessons are never exported, and imported lessons are shared like the world’s pool.

  • Local evidence moves them. When the outcome loop writes a trusted lesson into the shared pool, the judge also sees the nearest imported lessons:

    • a local lesson whose rule matches an imported one cites and confirms it, and serves in its place;
    • a local lesson the judge finds contradicting one retires it (an upstream_events contradicted row). Importing the same version again leaves it retired.

    A person’s private work never confirms or retires imported lessons.

Serving needs no model provider. Readiness learned reports each imported bundle (name, generation, signed or unsigned), how many lessons were imported, served, observed, confirmed locally and retired, and the last import.

Bundles are proprietary by default:

Field Default value
license LicenseRef-<publisher>-proprietary
redistribution none
commercial_use none
access.model private

--terms-url and --terms-file record the terms of use. The file is recorded as a hash. Opening a bundle is always an explicit flag:

  • --open opens the whole bundle. It sets CC-BY-4.0, redistribution: allowed, commercial_use: allowed and access.model: open. Attribution is required either way.
  • --open-slice <id> opens one slice and leaves the rest proprietary. For example, --open-slice tier:core marks the core tier as the open core.

The slices list in the manifest has one tier slice per tier (tier:core, tier:standard and tier:full) and one slice per domain (domain:forecast, domain:code, domain:conventions and so on). Each slice lists its item keys, a digest, and its own open, license and access values.

Each slice’s access decides what an import needs. The next section describes the rules. Licence terms beyond access, such as redistribution and attribution, are the operator’s to honour: the code records and displays them. Nothing is encrypted. access.encryption and access.entitlement are reserved and always null.

An import writes an item only when one of its slices allows it:

Slice access What the import needs
open Nothing. No check runs and no network call is made.
token (paid) An entitlement grant that names the slice.
private (the default export) A grant that names the slice, or --own: the operator’s assertion that the pack is their own, for an internal move or a backup. --own never unlocks a token slice.

Without a grant, an import writes the open items and reports the rest as withheld. It refuses when every selected item is withheld. --slices limits an import to named slices; a named token or private slice without a grant is refused. A withheld or unselected item is never retired, so importing only the free core later leaves paid items imported earlier in place. Every grant used is recorded in upstream_events.

A grant comes from an entitlement token, marina.entitlement.v1. The publisher, or an issuer listed in access.entitlement_issuers, signs it with its learned-bundle key. It is verified offline against the same pinned keys as bundles, so an import still works air-gapped. Payment happens outside Marina: a checkout run by the publisher issues the token.

The publisher issues a token for one buyer:

Terminal window
MARINA_LEARNED_SIGNING_KEY=… bun run learned entitle --artifact marina-memory:…/curated \
--tiers tier:standard --licensee buyer-co --versions '^1.0.0' --days 365 > buyer.json

The buyer imports the paid slice:

Terminal window
MARINA_UPSTREAM=on DB_PATH=marina.db bun run learned import ./packs/curated-1.1.0 \
--slices tier:standard --entitlement buyer.json

A token names the artifact, a semver range of versions, the slice ids it grants, a licensee label, an optional federation audience, a validity window and a nonce. A publisher revokes a token with a signed revocation entry carrying entitlement_nonce; an import with a revoked token is refused.

src/learned/entitlement.ts defines the EntitlementVerifier interface, so other proofs can produce grants too. The optional extensions/marina-market extension adds a read-only on-chain licence check. access.chains lists the chains on which a publisher sells such licences.

A world travels as a learned bundle whose content_profile is marina.world.v1. There is no separate format: world content is two item kinds, so worlds get content hashes, slices, diffs, revocation and the scanner like every other item.

  • world: the data-only world document. Unknown keys are refused, so a seed, lifecycle hook or bootstrap command cannot ride along.
  • room_source: room source code as inert text with requires_gate: world.code. Import stores it in upstream:room-sources for review and never compiles, registers or runs it. Installing it is the existing world.code-gated path, taken by someone who read it.

Imported world documents land in upstream:worlds with trust imported. src/learned/assemble.ts builds and signs a bundle from items a publisher already holds, such as a world.

A publisher revokes with a separately signed revocations.json:

{
"schema": "marina.learned.revocations.v1",
"publisher_key_id": "sha256:…",
"issued_at": "2026-10-04T00:00:00Z",
"entries": [
{ "artifact_id": "marina-memory:…/curated", "version": "1.1.0", "reason": "…", "severity": "critical" },
{ "artifact_id": "marina-memory:…/curated", "item_key": "lesson:…", "reason": "…", "severity": "retire" }
],
"signature": { "algorithm": "Ed25519", "publicKey": "…", "keyId": "sha256:…", "value": "…" }
}

You can supply a revocation list in two ways:

  • with --revocations <file> on import and verify;
  • as revocations.json placed beside the bundle.

Revocation lists are handled as follows:

  • A revocation list must verify against a pinned key, and that key must be the list’s publisher_key_id.
  • A list only affects artifacts of its own publisher.
  • An entry without version revokes every version of the artifact.
  • A revoked version is refused.
  • A revoked item is dropped and, if it was already imported, retired locally with status revoked.

signRevocations in src/learned/bundle.ts builds a signed list.

Bundles are signed with a dedicated key, MARINA_LEARNED_SIGNING_KEY. It is never the federation key or the arena key. Signatures are verified only against these pinned keys:

  • BUILTIN_PUBLISHER_KEYS in src/learned/sign.ts, which is empty until a publisher key is pinned in a reviewed change;
  • the operator’s MARINA_LEARNED_PUBLISHER_KEYS, a comma-separated list of label=<base64 SPKI> entries.

The public key embedded in a signature is never trusted on its own.

An instance key, for internal packs between your own worlds:

Terminal window
bun run learned keygen ~/.marina/learned.key

The key file is written with mode 0600 and the public key is printed. The exporting world sets MARINA_LEARNED_SIGNING_KEY=$(cat ~/.marina/learned.key); the importing world sets MARINA_LEARNED_PUBLISHER_KEYS=mine=<printed public key>.

A publisher key for public packs. A publisher key, such as an organisation’s key for public packs, is created offline by its owner. It is never generated by an agent or kept on a server. The steps are:

  1. On an offline machine, run openssl genpkey -algorithm ed25519 -out publisher.pem.
  2. Run openssl pkey -in publisher.pem -pubout -outform DER | base64 -w0 to get the public key to pin.
  3. Keep publisher.pem offline, mode 0600, with a backup under two-person control.
  4. Sign each pack on that machine. MARINA_LEARNED_SIGNING_KEY accepts PEM with escaped newlines.
  5. Pin the public key: in BUILTIN_PUBLISHER_KEYS through a reviewed change, or per install through MARINA_LEARNED_PUBLISHER_KEYS.

Key rotation, where the old key signs a statement naming the new one, and countersignatures are later phases.

learned is a read-only command available at rank 0:

Command Shows
learned The import mode and counts of artifacts, items, confirmations, seeds and priors.
learned artifacts Each imported version with its licence, access and key.
learned items [artifact:<id>] [status:active|retired|revoked] Each item with its trust (imported) and whether it is confirmed.
learned seeds Each seeded slot, and whether a local default shadows it or it is withheld pending approval.
learned priors [family:<f>] Each prior, with its raw counts and its weighted counts.
learned events [limit:<n>] The audit trail.

Readiness learned (readiness, GET /api/readiness) summarises the same state, with what MARINA_UPSTREAM allows.

Export and import stay operator acts.