Prefer Swagger UI? Click hereThe same API in the classic OpenAPI explorer.
// reference · 14 min read

Brain Packs API

Every endpoint for publishing a brain as a Pack, listing and granting Packs, and mounting Pack versions under another brain: POST /v1/brains/{id}/publish, the /v1/packs collection and its grants, and /v1/brains/{id}/mounts. All require a Bearer API key (api:read to read, api:write to mutate). Recall (POST /v1/brains/{id}/recall) is unchanged on the wire but composes native and mounted candidates into one ranked list. Cross-tenant access returns 404, never 403.

Availability

Brain Packs sit behind a second platform flag (RINGSIDE_BRAIN_PACKS_ENABLED) on top of the Brains flag (RINGSIDE_BRAINS_ENABLED). Both must be on. Where either is off, every endpoint on this page returns 404 resource_not_found and recall runs its normal single-brain path. As of this writing the Packs flag is off in production: build against this contract, but expect 404 until it is flipped on. A brain with no mounts is byte-identical to a brain that never heard of Packs.

Not parent_brain_id or "sub-brains"

There is no parent_brain_id, no brain inheritance and no "sub-brains" feature. That earlier proposal was superseded by Brain Packs: a brain does not inherit from a parent brain, it mounts an immutable, versioned Pack published from another brain. If you came here searching for sub-brains or brain inheritance, this page is the replacement. See the Brain Packs concept for the model.

The three objects

A brain is the living, writable, single-tenant store you already know from the Brains API. A brain_pack is an immutable, versioned snapshot minted from a brain. A brain_mount is a brain reading one Pack version as a read-only base layer. Writes only ever land in a brain's own native layer; a Pack and a mount are read-only.

Object: brain_pack

{
  "id": "pack_7f3a2c...",
  "object": "brain_pack",
  "name": "platform-runbooks",
  "slug": "platform-runbooks-a1b2c3",
  "source_brain_id": "brn_base...",
  "embedding_model": "openai/text-embedding-3-small",
  "embedding_dims": 1536,
  "visibility": "private",
  "encryption_at_publish": "none",
  "latest_version": 3,
  "retired_at": null,
  "created_at": "2026-06-26T10:00:00.000Z",
  "updated_at": "2026-06-26T12:00:00.000Z"
}

A GET /v1/packs/{packId} adds a versions array of brain_pack_version objects. embedding_model and embedding_dims are frozen at first publish and every consumer brain must match them. encryption_at_publish is one of none, standard, strict; only none (plaintext) Packs can take a reference mount.

Object: brain_pack_version

{
  "id": "pkv_91ad...",
  "object": "brain_pack_version",
  "pack_id": "pack_7f3a2c...",
  "version": 3,
  "status": "ready",
  "entry_count": 412,
  "chunk_count": 1380,
  "byte_size": 5912304,
  "notes": "Q3 runbook refresh",
  "published_by": "usr_dev...",
  "published_at": "2026-06-26T12:00:00.000Z"
}

A version is immutable: it never changes after publish, and updating the base means publishing a new one. status is building, ready or failed; a mount can only target a ready version. version numbers are allocated serially (1..N) under a row lock, so concurrent publishes never collide.

Object: brain_mount

{
  "id": "mnt_44c1...",
  "object": "brain_mount",
  "brain_id": "brn_acme...",
  "pack_id": "pack_7f3a2c...",
  "mode": "reference",
  "pinned_version": 3,
  "follow_latest": false,
  "scope_map": { "/": "/shared" },
  "status": "ready",
  "materialized_version": null,
  "created_at": "2026-06-26T12:30:00.000Z",
  "updated_at": "2026-06-26T12:30:00.000Z"
}

mode is reference or materialize. pinned_version is the Pack version this mount reads. status is ready immediately for a reference mount; a materialize mount starts pending and flips to ready once the async import finishes (materialized_version is then set to the imported version). scope_map rebases the Pack's scopes into the consumer tree.

Publish a brain as a Pack

POST /v1/brains/{id}/publish (scope api:write). Snapshots the brain's current native memory (entries, chunks, the already-computed vectors, the graph of entities and edges, and the frozen policy and live tier weights) into the next immutable Pack version. The first publish of a brain creates the Pack (one Pack per source brain in v1); each later publish bumps the version. Only native rows are published: a Pack is the brain's own memory, never the bases it itself mounts.

Composition limit: no two live shared layers

Because publish includes only native rows and a brain holds at most one reference mount, you cannot stack two live shared layers (for example a platform base plus a reseller base) under one brain. Two-level sharing (one base under many tenant brains) is fully supported. For a reseller / three-level shape where the client needs its own key, materialize the second layer over a reference-mounted base, or wait for the planned include_mounts publish flag. The guide Share knowledge across tenants walks the full pattern, the workaround and the roadmap.

name
Optional. Human label for the Pack. Defaults to the source brain name. Used only when the Pack is first created.
slug
Optional. Catalog handle, unique across your Packs. Auto-derived from name when omitted. A collision returns 409 slug_taken. Set only at Pack creation.
visibility
Optional. One of private, org, unlisted, public. Default private. v1 honours private; the others are reserved for the future catalog. An invalid value returns 400 invalid_visibility.
notes
Optional. Free-text changelog stored on this version.
encryption_at_publish
Optional. One of none, standard, strict. Defaults to mirror the source brain's encryption. Set only at Pack creation; later versions inherit the Pack's frozen value. A reference mount requires none. An invalid value returns 400 invalid_encryption.
declassify
Optional boolean, default false. Required (set true) to publish a sealed brain (standard or strict) to a plaintext Pack (encryption_at_publish=none), since that decrypts and stores the content in the clear. Without it the call returns 409 declassification_required. Only checked at Pack creation.
curl https://api.fightclub.pro/v1/brains/brn_base/publish \
  -H "Authorization: Bearer $RINGSIDE_KEY" \
  -d '{ "name": "platform-runbooks", "slug": "platform-runbooks",
        "encryption_at_publish": "none", "notes": "Q3 base" }'

Snapshots at or below 1,000 native entries build synchronously and return status: "ready" with final counts. Larger ones return status: "building" with chunk_count: 0; poll GET /v1/packs/{packId} until the version reads ready (or failed). A reference mount can only attach once the version is ready.

Publish refuses, it does not scrub

Publish runs two refusal gates before any Pack row is created. Oversize snapshots are rejected with 409 pack_too_large (caps: 50,000 entries via RINGSIDE_PACK_MAX_ENTRIES, 200,000 chunks via RINGSIDE_PACK_MAX_CHUNKS). Content matching a secret pattern (API keys, private keys and the like) is rejected with 422 secret_detected, naming the offending entry or chunk and scope. This is a deny gate, not a redactor: it will not silently strip a credential, it refuses the whole publish so you remove the secret from the brain and republish. Credentials belong in a secret store, never in a brain or a Pack. Because the scan decrypts under the brain key first, a sealed brain cannot smuggle a secret past it.

StatusCodeWhen
400invalid_visibilityvisibility not in {private, org, unlisted, public}.
400invalid_encryptionencryption_at_publish not in {none, standard, strict}.
409declassification_requiredPublishing a sealed brain to a none Pack without declassify=true.
409slug_takenThe requested slug is already used by one of your Packs.
409pack_too_largeNative entries over 50k or chunks over 200k (env-overridable).
422secret_detectedAn entry or chunk matched a secret pattern. The publish is refused.

List and inspect Packs

  • ·GET /v1/packs (api:read) returns { "object": "list", "data": [ brain_pack, ... ] } for every Pack you own or have been granted.
  • ·GET /v1/packs/{packId} (api:read) returns one brain_pack plus its versions array. A Pack you neither own nor are granted returns 404 resource_not_found (never confirming the id exists).
curl https://api.fightclub.pro/v1/packs/pack_7f3a2c \
  -H "Authorization: Bearer $RINGSIDE_KEY"

Retire a Pack

DELETE /v1/packs/{packId} (api:write, owner only) is a soft-retire, not a hard delete and not a 409. It hides the Pack from new mounts and stamps retired_at. Existing reference mounts keep reading their pinned version: a version that is still pinned by a live reference mount is never physically deleted. Content is garbage-collected only when the last reference mount on a version drops or re-points away. Retained versions keep billing the publisher's storage, which is the economic pressure to deprecate without forcing any consumer to churn.

curl -X DELETE https://api.fightclub.pro/v1/packs/pack_7f3a2c \
  -H "Authorization: Bearer $RINGSIDE_KEY"

Grants (cross-account mount access)

Grants exist only for cross-account sharing. Intra-account mounts, where the publisher and consumer share the same account, need no grant: ownership authorizes them. A grant lets another account mount your private Pack.

  • ·GET /v1/packs/{packId}/grants (api:read, owner only) lists grants as { "object": "list", "data": [ { "id", "object": "brain_pack_grant", "pack_id", "account_id", "created_at" } ] }.
  • ·POST /v1/packs/{packId}/grants (api:write, owner only) with { "account_id": "usr_..." } grants mount access. Granting your own account returns 400 self_grant; a missing body field returns 400 missing_account_id; an unknown account returns 404 account_not_found.
  • ·DELETE /v1/packs/{packId}/grants/{granteeId} (api:write, owner only) revokes a grant. After revocation that account can no longer create new mounts against the Pack.
# grant
curl https://api.fightclub.pro/v1/packs/pack_7f3a2c/grants \
  -H "Authorization: Bearer $RINGSIDE_KEY" \
  -d '{ "account_id": "usr_partner..." }'
# -> { "object": "brain_pack_grant", "pack_id": "pack_7f3a2c...", "account_id": "usr_partner...", "granted": true }

# revoke
curl -X DELETE https://api.fightclub.pro/v1/packs/pack_7f3a2c/grants/usr_partner \
  -H "Authorization: Bearer $RINGSIDE_KEY"
# -> { "pack_id": "pack_7f3a2c...", "account_id": "usr_partner...", "revoked": true }

Mount a Pack under a brain

POST /v1/brains/{id}/mounts (api:write). Mounts one Pack version onto the brain as a read-only base layer.

pack_id
Required. The Pack to mount. A Pack you cannot reach (not owned, no unrevoked grant) returns 404. Missing returns 400 missing_pack_id.
mode
Optional, default reference. reference reads the Pack version live cross-store with no per-tenant copy (the default, scales to many tenants). materialize imports a re-sealed copy into this brain (premium per-tenant isolation). An invalid value returns 400 invalid_mode.
version
Optional, default the Pack's latest. Must be a number (400 invalid_version) that exists (400 invalid_version) and is ready (409 pack_version_not_ready).
follow_latest
Optional boolean, default false. When true, a new publish re-points a reference mount (cheap) or re-materializes a materialize mount (atomic) onto the new version. When false (the default) the mount stays pinned to its version.
scope_map
Optional, default { "/": "/" }. A flat { source: target } map of valid scope paths, applied by longest-prefix match, that rebases the Pack into the consumer tree. A malformed map returns 400 invalid_scope_map.
curl https://api.fightclub.pro/v1/brains/brn_acme/mounts \
  -H "Authorization: Bearer $RINGSIDE_KEY" \
  -d '{ "pack_id": "pack_7f3a2c", "mode": "reference",
        "scope_map": { "/": "/shared" } }'

Validation runs in this order: embedding parity, then Pack state, then mode rules and caps. The embedding_model and embedding_dims of the Pack must equal the consumer brain's, or the mount returns 422 embedding_model_mismatch, because the two stores have to share one cosine space. A reference mount additionally requires a plaintext Pack and is intra-account only in v1; materialize carries no such restriction and is the path for cross-account consumers.

StatusCodeWhen
400missing_pack_idpack_id absent or empty.
400invalid_modemode not in {reference, materialize}.
400invalid_versionversion is not a number, or names a version that does not exist.
400invalid_scope_mapscope_map is not an object of valid { source: target } scope paths.
404resource_not_foundThe Pack is not owned and not granted to you (no id confirmation).
422embedding_model_mismatchPack and brain embedding model or dims differ.
409pack_retiredThe Pack is retired and cannot take new mounts.
409no_published_versionThe Pack has no published version to mount.
409pack_version_not_readyThe target version is still building or failed.
409mount_existsThis Pack is already mounted on the brain.
409mount_limitThe brain already holds the cap of 16 mounts (any mode).
409reference_requires_plaintext_packA reference mount on a Pack whose encryption_at_publish is not none.
409cross_account_reference_unsupportedA reference mount on a Pack you only hold via a grant. Use mode=materialize (cross-account reference is v2).
409reference_mount_limitThe brain already has its one allowed reference mount (materialize mounts are unlimited).
reference is ready, materialize imports

A reference mount is status: "ready" the moment it is created: recall starts reading the Pack version live on the next call. A materialize mount returns status: "pending" and imports the Pack's rows into the brain asynchronously, re-sealed under the consumer key; poll GET /v1/brains/{id}/mounts until status is ready and materialized_version is set.

List, re-point and unmount

  • ·GET /v1/brains/{id}/mounts (api:read) lists the brain's mounts as { "object": "list", "data": [ brain_mount, ... ] }.
  • ·PATCH /v1/brains/{id}/mounts/{mountId} (api:write) with { "version"?, "follow_latest"? } re-points a mount. Changing version re-points a reference mount in place, or re-materializes a materialize mount atomically (import-new, flip, delete-old, so recall never sees a half-swapped base). A non-number version returns 400 invalid_version; an unknown mount returns 404.
  • ·DELETE /v1/brains/{id}/mounts/{mountId} (api:write) unmounts. For a materialize mount it drops the imported rows; for a reference mount it releases the pin (which may trigger GC of a retired version).
# pin to an explicit version
curl -X PATCH https://api.fightclub.pro/v1/brains/brn_acme/mounts/mnt_44c1 \
  -H "Authorization: Bearer $RINGSIDE_KEY" \
  -d '{ "version": 3 }'

# or have it track new publishes automatically
curl -X PATCH https://api.fightclub.pro/v1/brains/brn_acme/mounts/mnt_44c1 \
  -H "Authorization: Bearer $RINGSIDE_KEY" \
  -d '{ "follow_latest": true }'

# unmount
curl -X DELETE https://api.fightclub.pro/v1/brains/brn_acme/mounts/mnt_44c1 \
  -H "Authorization: Bearer $RINGSIDE_KEY"
# -> { "id": "mnt_44c1...", "object": "brain_mount", "deleted": true }

Recall composes native and mounted content

POST /v1/brains/{id}/recall is unchanged on the wire (same body, same hit shape as the Brains API). When the brain has mounts, recall gathers candidates from the brain's own store and from each mounted Pack version, remaps scopes through the mount's scope_map, scores everything in one cosine space, and returns one ranked list. A brain with no mounts takes the identical single-store path it always did.

With explain: true, each hit carries a provenance object naming the store it came from: { "store": "native" } for the brain's own memory, or { "store": "pack", "pack_id": "pack_...", "version": 2 } for a mounted row. That is how you attribute a recalled fact to the tenant's layer versus a specific Pack version.

curl https://api.fightclub.pro/v1/brains/brn_acme/recall \
  -H "Authorization: Bearer $RINGSIDE_KEY" \
  -d '{ "scope": "/acme/q3", "query": "how do we roll back a failed deploy",
        "explain": true }'

How the two layers compose

Mounted content is reference knowledge: static, curated, evergreen. Native rows are episodic memory that ages and reinforces. Four rules govern how they compose, keyed on each row's provenance.

  1. 01Evergreen reference scoring. A mounted row skips recency and usage (both forced to 1.0) and keeps cosine x tier_weight x graph. The shared base does not rot or get reinforced like the tenant's own memory.
  2. 02The Pack carries its own tiers. A mounted row resolves its tier weight from the Pack version's frozen taxonomy and the publisher's snapshotted tier weights; native rows use the consumer brain's own policy. A tier-A gotcha keeps its authored importance even if the consumer never defines that type.
  3. 03Reserved native slots, relevance-gated. A native hit claims a reserved top-k slot only when it clears a relevance threshold; reference rows backfill when native is sparse or off-topic. The tenant's memory surfaces when it is genuinely relevant, the static base cannot crowd it out, and a near-empty new tenant wastes no slots.
  4. 04Native shadows reference. A native entry suppresses or outranks its Pack twin when it explicitly supersedes it (via the entry's supersedes provenance) or shares the same scope and a normalized entity or title token. The tenant's site-specific override beats the shared default. Semantic-only contradictions with different wording are not caught in v1.
Autotune ignores mounted rows

Recall feeds the consumer brain's autotune, but mounted rows never drive it: they are shared, evergreen and Pack-tiered, not the tenant's authored memory. observe and the learn loop only ever write native rows; mounted content is never mutated by learning.

Authorization and tenancy

Every endpoint here goes through the same Bearer-key auth and owner-scoped 404 as the rest of Ringside. Intra-account mounts, where the publisher and consumer share one account, are authorized by ownership and need no grant; this is the primary multi-tenant case (one platform account holding many tenant brains). Cross-account access requires an unrevoked grant, and in v1 a cross-account consumer must use materialize (cross-account reference is deferred to v2). A brain's recall reads only the exact pinned or materialized Pack version of its mounts, never arbitrary Pack content.

Billing

  • ·Publish counts the Pack version's content toward the publisher's storage at the normal storage rate. No embedding charge: vectors are copied, not recomputed.
  • ·A reference mount adds no consumer storage. The base is stored once by the publisher and read live; recall reads the shared index, which is compute, not storage.
  • ·A materialize mount counts the imported copy toward the consumer's storage, again storage-rate only, because vectors are reused rather than re-embedded.
  • ·Retained Pack versions keep billing the publisher's storage, which is what pressures deprecation without forcing a consumer to churn off a version it still pins.