Manifest reference
The manifest is a single local JSON file that declares ContextCake’s profiles,
project mappings, layer stacks, and Pack assignments. Every command that resolves
knowledge is pointed at one with --manifest.
ContextCake still reads the original flat layers shape without rewriting it.
The canonical v2 shape adds a required default profile. resolver.mjs,
mcp-server.mjs, write.mjs, live capture, telemetry, and profile-aware
promotion all select one profile before they open or write a source.
Current flat schema
Section titled “Current flat schema”{ "layers": [ { "name": "personal", "level": 3, "source": "okf-local", "path": "~/kb-personal" }, { "name": "team", "level": 2, "source": "okf-local", "path": "~/kb-team" }, { "name": "company", "level": 0, "source": "mcp", "command": "node", "args": ["./company-graph-server.mjs"] } ]}The main top-level key is layers, an array of layer objects. The local Pack manager
may also maintain a packs registry of installed versions and assignments. That
registry is bookkeeping for rollback; resolution reads only explicit layer entries.
The shared profile selector treats a flat manifest as an in-memory virtual profile
with id default. Selection does not alter the file. Creating the first additional
profile is the deliberate migration point described below.
Manifest v2: Project Profiles
Section titled “Manifest v2: Project Profiles”Canonical v2 moves every runnable layer into one profile and stores local project folder mappings separately:
{ "profiles": { "default": { "label": "Default", "layers": [ { "name": "personal", "level": 3, "source": "files", "path": "/Users/you/Notes" } ] }, "payments": { "label": "Payments", "layers": [ { "name": "repo", "level": 3, "source": "github", "repo": "acme/payments", "paths": ["docs/**"] }, { "name": "team-pack", "level": 0, "source": "okf-local", "path": "packs/team/1.0.0" } ], "pendingSources": [] } }, "projects": { "/Users/you/Code/payments": "payments" }, "packs": {}}The v2 rules are intentionally strict:
profiles.defaultis required and cannot be deleted.- Profile ids are stable lowercase slugs of at most 63 characters. Changing a
visible
labeldoes not change the id. - Each profile owns a complete
layersarray. Layer names need to be unique only within that profile. projectsmaps absolute, machine-local folders to profile ids. The paths are never uploaded by settings sync.pendingSourcesholds synced descriptors that are incomplete or not yet trusted on this machine. The engine never treats them as runnable layers.- Canonical v2 has
profilesand no top-levellayers.
Selection order
Section titled “Selection order”Profile-aware commands use one deterministic order:
- An explicit
--profile <id>wins. - Otherwise, the deepest canonical project folder containing the process working directory wins. Matching uses path segments, not a raw string prefix.
- With no match, the required
defaultprofile wins.
An explicit or matched unknown profile fails closed. It never falls through to unrelated default context. Symlink aliases are resolved to real paths; two equally specific aliases that name different profiles are a configuration error.
The selection is bound for the lifetime of an MCP process. Changing a mapping or the app’s visible profile does not redirect an already-running agent. Restart the agent session after configuration changes. Pending capture and promotion approvals also revalidate the manifest revision and live/target layer identities before they write, and hold the manifest mutation lock through the local write boundary.
Migration and transitional manifests
Section titled “Migration and transitional manifests”Existing flat manifests are not migrated on read. Creating the first additional profile performs one locked transaction:
- Re-read and validate the latest manifest.
- Write and verify a mode-
0600backup whose filename contains a UTC timestamp and SHA-256 of the original bytes. - Move the exact flat layer array to
profiles.default.layers. - Convert default-stack Pack assignments to profile
defaultand quarantine incomplete synced source descriptors inpendingSources. - Validate the complete candidate and atomically replace the manifest.
Some current settings-sync and Pack combinations can contain both top-level
layers and profile metadata. ContextCake recognizes that as a transitional
shape and continues running the flat stack until explicit normalization. General
writers reject newly created split-brain documents; only compatibility operations
may update a shipped transitional file.
Profile and source mutations share one adjacent lock file, so concurrent Pack, source, mapping, and profile operations cannot overwrite one another. Pack assignment, active version, precedence, origin, and layer references are validated as one contract before an atomic write.
Cache identity
Section titled “Cache identity”Profile-aware cache entries use an opaque SHA-256 fingerprint derived from the profile id and canonical source configuration. The fingerprint includes source kind and name plus its local root, repository/ref/path selection, endpoint, MCP command/arguments, and adapter options as applicable. Raw local paths do not appear in the cache namespace. Equal layer names in two profiles, renamed sources, and the same repository at different refs therefore cannot share cached content.
| Field | Required | Applies to | Meaning |
|---|---|---|---|
name |
yes | all | Layer identifier. Used in provenance (sourceLayer, contributors) and as the layer argument to read_file. |
level |
yes | all | Precedence. Higher wins per section. Personal is 3, Team is 2, Company is 0 by convention, but any integer works (the console shows this as a cascade position — #1 wins — and rewrites levels when you reorder). |
source |
no | all | okf-local (default when omitted), files, github, or mcp. |
path |
for okf-local / files |
okf-local, files |
Directory of the OKF bundle or existing document folder. |
command |
for mcp |
mcp |
Executable to spawn as a stdio MCP server. |
args |
for mcp |
mcp |
Argument array passed to command. |
repo |
for github |
github |
owner/name of the repository to read. |
ref |
no | github |
Branch or tag to read. Defaults to the repository’s default branch. |
paths |
no | github |
Glob selectors for which files become concepts. Replaces the defaults when set. |
auth |
no | github |
A credential reference — "keychain:<alias>" or {"tokenEnv": "NAME"}. Never a token. |
apiBase |
no | github |
GitHub Enterprise API base. Must be HTTPS with no userinfo, query, or fragment; HTTP is accepted only for loopback development. |
cache |
no | all | { "ttlSeconds": N, "dir": "..." }. Strongly recommended for github. |
Precedence is by level
Section titled “Precedence is by level”When a concept exists in more than one layer, the resolver merges it per section:
the highest level that speaks to a given section wins that section, and everything
else is inherited from below. Levels are integers you choose — higher is more
authoritative. When two contributors have the same level, the most recently
updated contributor wins that horizontal tie; array order is not an extra
precedence rule.
See Merge semantics and Layer cake for how precedence plays out across sections.
Source types
Section titled “Source types”A layer’s source decides how its knowledge is loaded. Each is read through the
same adapter interface, so they stitch into one effective graph.
okf-local (default)
Section titled “okf-local (default)”An OKF bundle: a directory of markdown files with YAML
frontmatter. The only required frontmatter field is type. Point path at the
directory.
{ "name": "team", "level": 2, "source": "okf-local", "path": "~/kb-team" }When source is omitted, okf-local is assumed:
{ "name": "team", "level": 2, "path": "~/kb-team" }A foreign knowledge graph reached over a stdio MCP server. ContextCake spawns
command with args, speaks MCP to it, and translates its responses into OKF at
read time — so a graph that was never OKF stitches in alongside your local bundles.
{ "name": "company", "level": 0, "source": "mcp", "command": "node", "args": ["./company-graph-server.mjs"] }See Foreign MCP sources for the adapter
contract and examples/mock-mcp-source/server.mjs for a runnable foreign source.
A local folder of existing Markdown, MDX, or plain-text documents. This is the
best starting point for repository docs, an Obsidian vault, or a Markdown wiki:
no ContextCake-specific frontmatter is required. Plain Markdown uses its first
# heading as the title and its ## headings as sections; files that already
have OKF frontmatter keep their full structured behavior.
{ "name": "work-notes", "level": 3, "source": "files", "path": "/Users/you/Documents/Obsidian" }github
Section titled “github”A repository read directly over the GitHub API — no clone, no checkout. The
markdown your team already keeps in the repo becomes a layer: by default
CLAUDE.md, AGENTS.md, README.md, docs/**, and .context/**. Documents
parse by exactly the same rules as files, so a CLAUDE.md on GitHub and one on
disk merge section-for-section instead of splitting into parallel sections.
{ "name": "payments-repo", "level": 3, "source": "github", "repo": "acme/payments", "paths": ["CLAUDE.md", "docs/**"], "auth": { "tokenEnv": "GITHUB_TOKEN" }, "cache": { "ttlSeconds": 900 }}Concept ids are repo-qualified — acme/payments/docs/runbook — so several repos
can be layered without colliding. Section dates come from each file’s last commit
rather than the repo’s last push, so staleness is per document. An explicit OKF
section date still wins; an OKF frontmatter date fills otherwise-undated sections
before the commit date is used.
paths accepts * (within one path segment), ?, and ** (spanning segments).
Setting it replaces the defaults rather than adding to them. Only .md, .mdx,
and .txt files are indexed, and only up to 2 MB per document — a larger
file is skipped and reported as a source warning rather than read into memory.
Subfolders ContextCake is not permitted to open are skipped and reported the
same way, so a source can be healthy and still be telling you it did not read
everything it was pointed at.
Reads are read-only and degrade rather than fail: if GitHub is unreachable, rate
limited, or the token lacks access, the layer warns on stderr and the remaining
layers still resolve. Add a cache block — a search sweeps every concept in
every layer, and the cache is what keeps that inside your API rate limit.
When a layer is invalid
Section titled “When a layer is invalid”A layer that fails validation is lifted out of the read path rather than failing the whole manifest: the app keeps answering, and the bad layer appears as a broken row carrying its error, so the screen you would use to fix it still loads. Nothing about the layer is relaxed or coerced — it contributes nothing, is never spawned, and its root is never watched.
Whole-manifest problems are still fatal, because those manifests are ambiguous
rather than broken: a duplicated layer name, or a second live layer, stops the
read outright rather than guessing which one you meant. Writes always validate
strictly, so an invalid layer can never be saved through a tolerated read.
You can remove a broken row from the app — the Remove action on that row edits the manifest for you, and it is the only action offered there, since renaming or syncing something that was never built could only fail. Because a manifest is only ever saved once the whole file validates, any removal that would leave a broken row behind is refused — including removing a source that is perfectly healthy, since the same write rewrites the whole file. The app therefore removes the broken rows alongside whatever you asked to remove, and names them before you confirm. Settings stay blocked until the manifest is valid again, and say so.
GitHub may truncate very large recursive tree responses. ContextCake refuses to index that partial response as if it were complete; it serves the last complete cached index when available, or resolves without that layer until a complete tree can be read. The source Sync action clears its TTL and immediately refreshes this remote index while preserving the separate clone-and-pull behavior of local Git sources.
Credentials
Section titled “Credentials”The manifest never holds a token. auth may only name one:
{"tokenEnv": "NAME"}— read the token from that environment variable for headless, CLI, and CI runs. Environment tokens are bound toapi.github.comby default. For GitHub Enterprise, list allowed API hosts in the comma-separatedCONTEXTCAKE_API_HOSTSenvironment variable."keychain:<alias>"— resolve an alias injected by a host application; the engine never opens a keychain itself. In ContextCake for Mac, connect a token under Settings → Connections, then use the exact alias shown there. The token is kept intokens.enc, encrypted with OS-keychain-backedsafeStoragewhen available, and only the alias and connection status return to the Console.
The object form must contain exactly the one tokenEnv field. Extra fields and
every other shape are rejected outright, so a raw credential cannot hide beside
an otherwise-valid reference. A repository you can read without a token needs no
auth at all. An alias with nothing injected reads anonymously, and /api/graph
reports authState: "missing-token"; a token withheld from the wrong host reports
authState: "host-mismatch".
Credentialed API requests never follow redirects. A stored token is also bound to
the API host recorded when it was connected, so changing apiBase cannot redirect
that token to a different host.
How paths resolve
Section titled “How paths resolve”path and any relative args (those starting with ./ or ../) resolve relative
to the manifest file’s own directory — not the current working directory. A manifest
at ~/config/layers.json with "path": "kb-team" reads ~/config/kb-team. Absolute
paths and non-relative args are passed through unchanged. This makes a manifest
portable: keep it next to the bundles it points at and it works from anywhere.
The trust boundary
Section titled “The trust boundary”An mcp layer runs command with args exactly as written. A manifest you did not
author can therefore execute arbitrary commands as your user the moment you resolve
against it. Treat the manifest the way you treat any MCP client config: only point
--manifest at files you trust. Read The trust boundary
before pointing a manifest at sources you didn’t write.
Credentials have an additional boundary. A github layer may set apiBase for
GitHub Enterprise, but ContextCake validates that URL and withholds any stored token
whose recorded API host does not match it. Credentialed requests also refuse
redirects. Review apiBase and auth together for configuration accuracy, but a
hostile apiBase cannot borrow a token connected for another host.
Pack-managed layers
Section titled “Pack-managed layers”A Pack installed with pack.mjs is an ordinary okf-local base layer with an origin
that records the Pack identity and active version:
{ "name": "pack-contextcake", "level": 0, "source": "okf-local", "path": "packs/contextcake/0.1.0",}Do not edit installed Pack directories. Put personal or team changes in a separate, higher-precedence layer. Updates switch only the Pack-managed layer path; rollback points it at a retained version; removal detaches it. None of those operations overwrite or delete another layer. In v2, every Pack assignment names its profile explicitly; the same retained immutable version may be attached to more than one profile.
The bundled demo manifest
Section titled “The bundled demo manifest”apps/playground/manifest.json is the three-layer, all-okf-local stack used by the
docs examples and the playground. The layers deliberately disagree so the merge and
conflict surfacing are visible:
{ "layers": [ { "name": "personal", "level": 3, "path": "demo-layers/personal" }, { "name": "team", "level": 2, "path": "demo-layers/team" }, { "name": "company", "level": 0, "path": "demo-layers/company" } ]}Resolve a concept against it:
node resolver.mjs --manifest apps/playground/manifest.json --concept decisions/primary-dbRelated
Section titled “Related”- CLI — every command that takes
--manifest - MCP tools — serving a manifest to an agent
- Your first cascade — build your own manifest