brain.aide Spec
brain.aide is the host project's brain configuration. It declares which MCP server to launch as the brain layer and carries the hand-written agent-facing instructions that explain how to use it. It lives at .aide/config/brain.aide and is the single source of truth for the host's brain wiring.
brain.aide is a config file, not an intent spec. Its frontmatter is a typed config object; its body is hand-written content that aide_brain returns one section at a time: the live sections at runtime, and the seed sections while /aide:brain config writes them into the brain via the brain's own MCP write tool at the paths the published contract names. The explicit divergence from the intent-spec family (intent.aide, plan.aide, todo.aide, research.aide) is load-bearing — see ## Divergence from intent specs below.
Format
---
name: obsidian
mcpServerConfig:
command: npx
args:
- "-y"
- "@bitbonsai/mcpvault@0.16.0"
-
---
<!-- aide-<marker>-start -->
...section body, written by the user...
<!-- aide-<marker>-end -->
<!-- ...one block per contract entry, in contract order. The section list,
marker names and purposes come from the published contract (see Body
shape below), not from this example... -->
Frontmatter fields
The schema is the minimum that still has real runtime consumers. Both fields are required.
-
name—string. A human-readable label for the wired brain (e.g."obsidian","notion","company-wiki"). The user reads it; the agent narrates it back in conversation ("I am connected to yourobsidianbrain"); the package never branches on its value. Two hosts may use the samenamewith structurally different launchers and that is fine —nameis descriptive metadata, not a dispatch key. -
mcpServerConfig—object. The MCP server configuration that wires the brain into the host's toolchain. Sync writes this object byte-for-byte into.mcp.jsonunder the fixedbrainkey. Its shape mirrors what.mcp.jsonexpects undermcpServers["brain"]so sync passes it through without reshaping.command—string. The executable to launch (e.g."npx","node","uvx").args—(string | null)[]. Arguments passed tocommand. Every element is either a fully-formed argument string OR YAML null. A YAML null at any index is the explicit unwired-slot signal — the user must fill that slot via/aide:brain configbefore sync will write the brain entry into.mcp.json. The default scaffold's bundledobsidianBrainAideTemplate()emits YAML null atargs[3](or the platform-equivalent path slot); the config section is the only place that documents which arg index means what for that backend. The package never introspects which slot is which. There is no literal-string placeholder —<BRAIN_PATH>,<API_TOKEN>, magic UUIDs, and any other in-band sentinel design have been retired in favor of structural YAML null.
That is the complete schema. There are no other top-level fields. A connector, rootPath, entryFile, or tools field — all of which a prior schema required — is rejected by the parser as malformed-frontmatter. Each retired field was validation theater: it existed for the parser to check, with no code path that consumed its value at runtime.
The parsed ok result flattens all fields as siblings: { kind: "ok", name, mcpServerConfig, ...one field per contract section }. There is no config: BrainAideConfig wrapper — frontmatter fields and body fields appear at the same level on the result object, and the body field names come from the contract.
Body shape
The body is a sequence of marker-bounded sections. The required sections are a published contract with one source: the server's brain contract. This doc teaches the model and deliberately does not copy the list, because a copy would drift from the parser that enforces it.
Where agents read the contract. aide_init's Brain config (brain.aide) step carries brainSummary.sections: each section's marker, its role (live or seed), the brain-relative seedPath for seed sections, and the byte size of the template's version. The summary carries no section text and no purposes. A section's one-line purpose comes back from aide_brain alongside the section itself. The array order is the required body order, and new required sections append; existing entries never reorder.
Marker grammar. Every section is one open/close pair: <!-- aide-<marker>-start --> ... <!-- aide-<marker>-end -->, where <marker> is a contract entry's marker. Markers are lowercase and case-sensitive, with single ASCII spaces around the token: <!-- aide-<marker>-start --> is valid; <!--aide-<marker>-start-->, <!-- Aide-<marker>-Start --> and <!-- <marker>-start --> are not. Pairs appear in contract order; any other order is a violation. Bytes outside any marker pair are silently ignored, so blank lines between sections are fine and do not affect parsing.
Any backend that satisfies the contract is valid. A hand-written config for a different brain (a wiki, a notes API, a document store) is as valid as the bundled Obsidian template. Nothing depends on the template's wording or on the backend's identity: the parser checks the frontmatter shape and the contract's marker pairs, and nothing else.
Live vs. seed split. The contract's role field divides the sections by consumer:
- Live sections are read through
aide_brainat runtime. They live inbrain.aidepermanently and are returned verbatim on demand. - Seed sections are written into the brain by
/aide:brain config, each at its contractseedPath, through the brain's own MCP write tool. The agent fetches each missing seed's bytes withaide_brain, one section per call. A seed write is skip-on-exists: a file already at that path is never fetched or overwritten. After seeding, the brain owns that file and users edit it there. The package never writes a seed into the brain; the seed bytes go dormant once the brain holds them.
Kanban content is a seed, not a feature of the package. The kanban board, a kanban index and one guide per kanban operation are ordinary seed sections: they land in the brain at their contract seed paths like any other seed, and after that the brain owns them. The package ships two kanban pieces, as a pair: the kanban pointer skill, installed with the other AIDE skills, and the kanban-maintainer board agent, installed with the other agents in its own kanban/ agent folder. The skill names the kanban index in the brain and routes every board read and write to the agent; the agent reads the index and only the guide for the operation requested, so no agent reads procedures it does not need, and it reports compactly or carries out board changes the caller has already approved. Both work in any project and do not require running the AIDE pipeline. The procedures themselves stay seeds — the package ships no board procedures of its own. Each backend's template fills these sections in its own vocabulary; the bundled one is written for Obsidian.
What each section is for. The role decides who uses the bytes:
- A live section is used where it sits. The orientation section is where the user writes runtime usage instructions; the config section is the backend's wiring flow that
/aide:brain configexecutes (which arg slot is unwired, how to land its value, how to run sync, how to seed the brain, what restart message to emit). - A seed section is used once:
/aide:brain configfetches it throughaide_brainand writes it verbatim at the contractseedPath.
The sync verb and boot reporter read frontmatter only; the body sections are irrelevant to them. The install summary reports which sections are missing or stale by marker, never by their bytes.
One section at a time. aide_brain returns exactly the section requested, never other sections or the whole file. With it comes found (true only when the text is that section's bytes) and, when found, the section's purpose from the published contract. It reads a section on every status where the file exists and parses — healthy, missing sections, unwired, drifted — because seeding and repair happen exactly while the config is incomplete.
Closed grammar: strict failure on layout violations. The parser rejects structural faults among the marker pairs that are present, each as malformed-body with its reason:
- Malformed or typo'd marker (uppercase, mixed-case, missing
aide-prefix, extra whitespace):"unknown marker: <as-written>". - Unmatched closer without a preceding opener:
"unmatched closing marker: ...". - Unmatched opener without a following closer:
"unmatched opening marker: ...". - Wrong section order:
"marker order violation: ...". - Nested markers:
"nested marker: ...".
Absent whole sections are a different result. When every present pair is sound but one or more required pairs are absent entirely, the parser returns missing-sections, naming each absent section in contract order. It is not a malformed-body reason. Structural faults among the present sections take precedence: a file with both is reported as the structural fault, so the user fixes the structure before the gaps are filled. The distinction drives the boot status; see ## Boot reporter brain status states.
Outdated is reported, never silently accepted. The policy for a present file that falls short of the contract:
- It is reported with its fault. The package never reads a short file as if it were complete.
/aide:brain configrepairs amissing-sectionsfile after the user confirms, by callingaide_upgradewithcategory: "brain". The tool inserts only the missing sections, byte-identical from the latest bundled template and in the file's line endings, and only when the config launches the template's own brain server. For any other backend it inserts nothing and names the sections, which theaide-brain-authorskill then writes with the user for that backend. Existing sections, launcher wiring and customizations are never touched.- Structural faults are shown to the user with the parser's reason, for hand repair. The package does not guess intent.
- Install never writes
.aide/config/after the first scaffold, and upgrade writes the brain config only through the consent-gated brain category. See## The .aide/config/ directory contract. - A retired-marker file (one whose sections use markers no contract entry names) reports every section as missing. There is no rename rewrite and no transitional read path.
Seed bytes flow from the seed sections. The seed sections are the source of truth for the artifacts the integration's aide-config prose writes into the brain at first /aide:brain config. The package does not hold these bytes as inline TypeScript constants; they live in brain.aide where the user can see, edit and own them in the scaffold. No package code path writes the artifacts to disk; the integration's prose owns the writes via the brain's own MCP write tool.
Substitution surface
The parser supports ${...} interpolation of frontmatter field names inside mcpServerConfig.args. At sync time, any ${fieldName} reference in args is expanded against the frontmatter's top-level fields and the resulting array is written into .mcp.json.
In the current schema, name is the only top-level field that resolves as a substitution source, so the surface is essentially dormant. The default scaffold makes no use of it — the path lives inline as a literal string after the user fills the unwired slot, or as YAML null at the unwired slot before that. The interpolation surface remains for advanced users who want to DRY a value across positions.
YAML null entries in mcpServerConfig.args pass through interpolateArgs unchanged at the same index — null is not a string, so there is no ${...} reference to expand. Sync's null-refusal precondition catches null-bearing args before they reach .mcp.json; sync never writes null into the launch command.
The substitution surface is mcpServerConfig.args and only mcpServerConfig.args. It runs only at sync time. It NEVER applies to any body section. All body sections return verbatim, byte-identical to what the user wrote between the recognized markers.
aide_brain parameters
aide_brain is read-only. Its optional source picks what it reads; omitted, it reads the host's config:
aide_brain({ kind?: <any section marker in the published contract> })
// → { status, instructions, found, purpose? }
aide_brain({ source: "template", kind?: <marker> | "launcher" })
// → { template, purpose }
aide_brain({ source: "contract" })
// → { sections: { marker, role, purpose }[] }
aide_brain({ source: "draft", path: <project-relative path> })
// → { outcome: "valid" | "invalid" | "absent" | "refused", instructions, fault? }
- Template read returns one section, or the launcher (the frontmatter between the
---fences), of the bundled template with its purpose, whatever state the host's config is in. - Contract listing returns every section in contract order with its role and purpose, and no text.
- Draft check parses a draft config at a path inside the project with the same parser and fault wording as the host check, so a
validdraft is structurally ready to swap in. A path outside the project is refused unread.
For a host read, kind defaults to "orientation"; any other contract marker, live or seed, selects that section, and a value outside the contract is refused. The reply:
found: true—instructionsis the requested section's bytes between its marker pair, verbatim, andpurposeis that section's one-line purpose from the published contract (never read from the user's file). This happens on every status where the file exists and parses:ok,brain-aide-invalidwith that section present,no-mcp-entryandmcp-drift.statusstill reports the brain's real state.found: false—instructionsis remediation andpurposeis absent. An absent file (no-brain-aide) routes to/aide:brain config. A requested section missing from the file namesaide_upgradewithcategory: "brain", the call that inserts it. A malformed or unreadable file gives the parser's reason, a hand fix with the user, and a re-check withaide_info.
Callers branch on found for "did I get the section" and on status for "is the brain healthy". Once a seed is in the brain, agents read the brain file directly through the brain's own MCP read tool; the seed bytes in brain.aide go dormant.
The .aide/config/ directory contract
brain.aide lives inside .aide/config/, which is the canonical home of user-owned configuration files in every host project. The directory has a sharp ownership rule:
-
Scaffolded once.
aide_init(andcli/initat cold install) creates.aide/config/brain.aideif it does not exist, pre-filled with the bundled template selected by--brain <integration>(default: the obsidian-flavoredobsidianBrainAideTemplate()). -
Never overwritten. After the first write, the file belongs to the user forever.
aide_initnever overwrites, patches or migrates anything under.aide/config/on subsequent runs; its brain step reports state only, as markers, flags and sizes. -
Changes are consented, one decision at a time. The only package writes to an existing
brain.aidecome fromaide_upgrade's brain category, which/aide:brain configdrives after the user's yes:aide_upgrade({ category: "brain" })inserts only missing sections and returns their markers.brainChange: <marker | "launcher">previews one change (before,after,beforeHash), read-only.- The same call plus that
beforeHashapplies that one change, refusing if the bytes changed at all since the preview.
A section is offered for migration only when its text matches a known earlier template version, so sections the user edited and custom launchers are never offered. No call ever rewrites, reorders or reformats a section outside the one change applied.
-
Boundary is the path. The install/upgrade tooling reads the directory path itself as the ownership signal — there is no per-file allowlist. Any file the user puts under
.aide/config/is safe from the package's install/upgrade machinery.
Files outside .aide/config/ (the methodology docs at .aide/docs/, pipeline command templates, agent definitions, skills, every other artifact the package ships) are package-owned and re-installed on every init/upgrade.
brain.aide is the current inhabitant of .aide/config/; the convention is forward-compatible with future user-owned config files.
Launch modes
Each MCP server entry — the aide server and the brain server declared in mcpServerConfig — launches in one of two modes. Setup asks which one, recommends global, and never switches an existing entry.
- Global — pick it on a personal machine. Install once with
npm i -g @aidemd-mcp/server; the entry runs the installed command by name, and terminal commands areaidemd-mcp initandaidemd-mcp sync. It stays on the installed version until you reinstall. Global is the fast launcher and the only one that works offline. - Local — pick it for shared repos and cloud/CI. The entry runs through npx, pinned to one exact version:
npx -y @aidemd-mcp/server@<version> sync./aide:upgrademoves the aide pin forward when you confirm it. Its first launch after a pin change downloads the package, and even a pinned, cached start still contacts the registry, so an npx cold start can miss Claude Code's 30-second connect window.
The brain server's global or local launcher lives in mcpServerConfig, and a local brain launcher is pinned the same way. /aide:brain config sets the launcher, offers the global install of the brain's own package, and moves the brain pin forward with your OK. A legacy brain launcher is updated through the brain category's brainChange: "launcher" preview and apply, which keeps the current launch mode.
Lifecycle
-
Created or repaired by
/aide:brain config. When.aide/config/brain.aideis missing, the command asks which backend the user wants. For the bundled Obsidian default it uses the templateaide_initreturns, which carries YAML null at the unwired launcher slot until step 2 (a coldcli/initinstall also scaffolds that template once, if the file is absent). For any other backend it hands off to theaide-brain-authorskill, which writes the launcher and every section with the user, section by section, from the contract listing and each section's template text andpurpose(aide_brainwithsource: "contract"andsource: "template"), checks the draft withsource: "draft", then swaps it in or leaves it beside an existing config. When the file exists but is outdated, the command repairs it as described under### Body shape. Either way the result is validated with the real parser throughaide_info; a hand-written file counts only if the parser accepts it. -
Filled by the user via
/aide:brain config. The config section's prose runs, asks the user for whatever values the YAML-null slots need (e.g. the brain root path for the bundled Obsidian default), and editsbrain.aideto land the resolved values into the args slots. Retargeting, switching brains, swapping the MCP launcher and rewriting any body section are also edits to this one file. -
Propagated to
.mcp.jsonby sync (aidemd-mcp syncfor a global install,npx -y @aidemd-mcp/server@<version> syncfor a local one — match the project'saideentry). The user runs this after the slot-filling step. Sync reads the frontmatter, expands any${...}interpolations inmcpServerConfig.args(the default scaffold has none), and writes the resulting object into.mcp.jsonunder the fixedbrainkey. Sync refuses null-bearing args: if any element ofmcpServerConfig.argsis still null, sync exits non-zero and routes the user back to/aide:brain config. Sync is the only mechanism that mutates.mcp.json's brain entry. -
Seeds written by the integration's
aide-configprose, after sync passes. For each seed missing from the brain, the prose fetches that section withaide_brain({ kind: <marker> })and writes it into the brain via the brain's own MCP write tool at the contractseedPath, skipping any file that already exists. The section's empty lines at its start and end are dropped, and a leading frontmatter block is passed in whatever form the backend's write tool expects (for example as a separate frontmatter argument) rather than left inside the body. The presence-check + re-seed loop owns missing-artifact recovery on every/aide:brain configinvocation. The kanban board, the kanban index and the operation guides are seeded here like any other seed section. -
Restart Claude Code so the new MCP server loads. MCP server registration is read at client startup; an edit + sync without a restart leaves the running session pointing at the previous brain.
-
Drift detected by the boot reporter. At session start, the reporter compares the
brainentry in.mcp.jsonagainst the values declared inbrain.aide. If they disagree, the reporter surfacesmcp-drift. The orchestrator treats drift as a hard halt: it does not proceed, does not attempt self-repair, and directs the user to run sync (aidemd-mcp syncfor a global install,npx -y @aidemd-mcp/server@<version> syncfor a local one — match the project'saideentry).
Re-running sync against an unchanged brain.aide is idempotent — it reads the file, computes the same target object, sees .mcp.json already matches, and exits reporting no change.
Boot reporter brain status states
The boot reporter surfaces exactly five brain status states. The orchestrator reads the state and decides whether the pipeline can advance.
-
ok—.aide/config/brain.aideexists and satisfies the contract,.mcp.jsonhas abrainentry, and the two agree. The pipeline proceeds. -
no-brain-aide—.aide/config/brain.aideis absent (ENOENT), and only that. A file that is present is never reported as absent; a present file that cannot be read (permissions, a directory at the path) isbrain-aide-invalidwith a reason naming the read error. Remediation: run/aide:brain config, which asks which backend to use and creates the file. The orchestrator halts until the file is in place. -
brain-aide-invalid— the file is present but falls short of the contract. The state carries afault:missing-sectionswith the absentsections(a file written against an earlier contract, or one missing a block), ormalformedwith the parser'sreason(unusable frontmatter, or a structural fault among the marker pairs). Remediation: run/aide:brain config, which inserts the missing sections after confirmation, or shows the reason for hand repair. The orchestrator halts until the file is valid. -
no-mcp-entry—brain.aideexists, but.mcp.jsonhas nobrainentry undermcpServers, ORmcpServerConfig.argscarries one or more YAML null entries (a null-bearing config is by definition not a wireable launch command, so it collapses semantically intono-mcp-entry). Remediation: run/aide:brain configto fill any null slots, then run sync (aidemd-mcp syncfor a global install,npx -y @aidemd-mcp/server@<version> syncfor a local one — match the project'saideentry) to write the entry from the source of truth, then restart Claude Code. The orchestrator halts until the wiring is in place. -
mcp-drift— bothbrain.aideand the.mcp.jsonbrainentry exist, but their values disagree (thecommanddiffers, theargsdiffer, or the user hand-edited.mcp.jsonaway from whatbrain.aidedeclares). Remediation: run sync (aidemd-mcp syncfor a global install,npx -y @aidemd-mcp/server@<version> syncfor a local one — match the project'saideentry) to bring.mcp.jsonback in line with the source of truth, then restart Claude Code. The orchestrator halts until the two agree. The boot reporter never auto-repairs drift — auto-repair would shift a load-bearing contract surface out from under the user and erase any deliberate hand-edit they made.
Divergence from intent specs
brain.aide shares its file extension with the intent-spec family but is not an intent spec. The following fields the intent-spec template requires are explicitly absent:
- No
scope—brain.aidehas no scope boundary. It is a project-wide config file; scoping it to a subdirectory has no meaning. - No
intentparagraph —brain.aidedoes not describe desired behavior for an agent to implement. It is runtime configuration, not an engineering contract. - No
outcomesblock — there are no desired or undesired outcomes to specify. The file's "outcome" is simply that the brain is correctly wired; correctness is verified by the boot reporter, not by an outcomes list. - No
descriptionfield — intent specs usedescriptionto name what the module does;brain.aideusesnameto label the brain type, which is descriptive metadata, not a description of what code does. - No
statuslifecycle field — intent specs trackdraft,aligned,in-progress,done;brain.aidehas no lifecycle phases of that kind. It is always "the current config."
Rationale. Brain configuration is runtime configuration; intent specs are engineering contracts. Conflating the two erodes the methodology's central distinction — every downstream agent that reads both types would have to decide which rules apply. The absence of intent-spec fields from brain.aide is a hard boundary, not an oversight.
Rules
-
All body sections returned verbatim.
aide_brainreturns any requested section, live or seed, byte-identical to disk. No server-side templating, no rendering pass, no variable substitution applies to any body section. -
One decision's worth of bytes at a time. The install summary carries no section text.
aide_brainreturns one section per call, andaide_upgrade's brain preview returns one before/after. No package code path writes a seed into the brain; the agent does, through the brain's own MCP write tool. -
aide_brainis read-only. It never writes, and itspurposecomes only from the published contract. The sync verb and boot reporter read frontmatter only for wiring. -
The section list has one source. The required sections live in the server's published contract and nowhere else. This doc, the templates' prose and agent prompts point to it and never restate it.
-
mcpServerConfig.argsinterpolation runs only at sync time. Any${fieldName}references inargsare expanded by sync (aidemd-mcp syncfor a global install,npx -y @aidemd-mcp/server@<version> syncfor a local one — match the project'saideentry) against the frontmatter and written into.mcp.json. They are never expanded at read time, at server startup, or inside any body section. -
The brain's interface contract is the
orientationsection, not the schema. The frontmatter does not declare expected MCP tool names, expected operations, or expected entry-point paths. The agent reads theorientationsection verbatim, calls whatever MCP tools it names, and surfaces failures fast. A frontmatter field whose only consumer is the parser's own validation is rejected. -
nameis descriptive metadata, never dispatched on. No code in this package branches on the value ofname. The field exists so a human reading the file immediately knows what kind of brain is wired and so the agent can narrate its connection in conversation. Introducing any code path that switches onnamere-introduces the in-code backend registry this architecture rejects. -
Adding a new brain is a host-side edit + sync, never a code change to this package. The package has no opinion about which brain a host runs — only about how the host declares it. A new brain = write a
brain.aidethat satisfies the contract (/aide:brain configcan author it), run sync (aidemd-mcp syncfor a global install,npx -y @aidemd-mcp/server@<version> syncfor a local one — match the project'saideentry), restart Claude Code. -
Drift is a hard halt. When the boot reporter surfaces
mcp-drift, the orchestrator stops. It does not auto-repair. It directs the user to run sync. -
The
.mcp.jsonbrain key is fixed. Sync always writes tomcpServers["brain"]. The key name is not configurable; downstream tooling, the boot reporter, and the orchestrator all read from the same fixed name. -
.aide/config/is user-owned forever.aide_initnever writes under.aide/config/after the first scaffold. New contract sections and migrations reach an existing host only through/aide:brain config, which the user runs; it applies each one throughaide_upgrade's brain category, after the user confirms. -
One file, one source of truth.
brain.aideis the single source of truth for brain configuration. The host's.mcp.jsonbrain entry is a derived artifact — it must matchbrain.aideand is only updated by sync. -
Outdated is reported, never silently accepted. A present file short of the contract surfaces as
brain-aide-invalid. Repair is/aide:brain configinserting only the missing sections; there is no transitional read path, no auto-injection and no heading-based guessing. Outdated section text and a legacy launcher are reported byaide_infoasbrainMigration(a soft notice, never a gate) and migrated only through/aide:brain config, one previewed and confirmed change at a time.
Placement
brain.aide lives inside the .aide/config/ directory at the project root:
.aide/
├── intent.aide ← project intent spec
├── config/ ← user-owned configuration (never overwritten after scaffold)
│ └── brain.aide ← brain configuration (this file type)
├── docs/ ← canonical methodology docs (package-owned, re-installed on init/upgrade)
│ └── brain-aide.md
└── ...
No cross-referencing between brain.aide and other .aide files is needed. The folder placement is the relationship: anything under .aide/config/ is user-owned config; anything elsewhere under .aide/ is methodology surface the package manages.