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.
- What a bundle contains
- Starting fresh from a bundle
- Export
- Verify, diff and import
- Serving imported lessons
- Licence, access and slices
- Entitlements for paid and private slices
- Worlds (
marina.world.v1) - Revocation
- Keys
- Inspecting imports in-world
What a bundle contains
Section titled “What a bundle contains”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
excludesbut 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,transcriptandnote_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>, ormarina-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.
Starting fresh from a bundle
Section titled “Starting fresh from a bundle”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:
marina learned distill ./old/marina.db --out ./packs/old-world --archiveStart a new world from the code seed:
marina startThen import the bundle into it:
MARINA_UPSTREAM=on DB_PATH=marina.db marina learned import ./packs/old-world --own --allow-unsignedmarina 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
excludesfield lists these. -
--outdefaults to<db>.learned.--namedefaults todistilled.--publisherdefaults toMARINA_NAME, elselocal. -
With
MARINA_LEARNED_SIGNING_KEYor--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 labelledunsigned. A signed bundle still needs its publisher key pinned. - it has no
-
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_UPSTREAMsays. Every other import rule applies: pinned signatures, revocations, no downgrade, trustimported, and seeds only for empty slots. MARINA_BOOTSTRAP_ACCEPTwidens what is accepted:owncovers your ownprivateslices (never a paidtokenslice);unsignedaccepts 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.
MARINA_BOOTSTRAP_BUNDLES=./packs/old-world MARINA_BOOTSTRAP_ACCEPT=own,unsigned marina startImported 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
Section titled “Export”Export is an operator script and never an in-world command:
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
generationto 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. |
Verify, diff and import
Section titled “Verify, diff and import”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.0MARINA_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:
- the schema is not
marina.learned.v1; - 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; - a file’s sha256 does not match the manifest, or an item file is present that the manifest does not list;
- an item does not parse, is in the wrong file, or has a
content_hashthat does not recompute; - the contents overreach the manifest (see
excludesabove).
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_byto 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. |
Serving imported lessons
Section titled “Serving imported lessons”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
trustedlesson 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_eventscontradictedrow). 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.
Licence, access and slices
Section titled “Licence, access and slices”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:
--openopens the whole bundle. It setsCC-BY-4.0,redistribution: allowed,commercial_use: allowedandaccess.model: open. Attribution is required either way.--open-slice <id>opens one slice and leaves the rest proprietary. For example,--open-slice tier:coremarks 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.
Entitlements for paid and private slices
Section titled “Entitlements for paid and private slices”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:
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.jsonThe buyer imports the paid slice:
MARINA_UPSTREAM=on DB_PATH=marina.db bun run learned import ./packs/curated-1.1.0 \ --slices tier:standard --entitlement buyer.jsonA 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.
Worlds (marina.world.v1)
Section titled “Worlds (marina.world.v1)”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 withrequires_gate: world.code. Import stores it inupstream:room-sourcesfor review and never compiles, registers or runs it. Installing it is the existingworld.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.
Revocation
Section titled “Revocation”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>onimportandverify; - as
revocations.jsonplaced 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
versionrevokes 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_KEYSinsrc/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 oflabel=<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:
bun run learned keygen ~/.marina/learned.keyThe 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:
- On an offline machine, run
openssl genpkey -algorithm ed25519 -out publisher.pem. - Run
openssl pkey -in publisher.pem -pubout -outform DER | base64 -w0to get the public key to pin. - Keep
publisher.pemoffline, mode 0600, with a backup under two-person control. - Sign each pack on that machine.
MARINA_LEARNED_SIGNING_KEYaccepts PEM with escaped newlines. - Pin the public key: in
BUILTIN_PUBLISHER_KEYSthrough a reviewed change, or per install throughMARINA_LEARNED_PUBLISHER_KEYS.
Key rotation, where the old key signs a statement naming the new one, and countersignatures are later phases.
Inspecting imports in-world
Section titled “Inspecting imports in-world”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.
