Brainy for agents
In this section
This page is for an agent (or the person configuring one) arriving at Brainy for the first time. It is the least configuration that gets you from npm install to a real tool call — not a tour of the engine. Everything named here is real: every example call and every answer shape below is the door's own real TypeScript shape, never invented.
Install and connect
npm install @soulcraft/brainy
npm install @soulcraft/brainy-serve-linux-x64-gnu # the native server + MCP binaryBASHnpx brainy mcp speaks MCP over stdio, against whatever brain store sits in the current directory. A directory with no store yet is a refusal naming the one command that makes one (npx brainy, the starter) — this engine reads and writes stores, it does not conjure one from an empty folder.
The client config is copied verbatim from MCP's own shape — the same one npx brainy prints for you on first run:
{
"mcpServers": {
"brainy": {
"command": "brainy",
"args": ["mcp"],
"env": { "BRAINY_KEY": "…" }
}
}
}JSONBRAINY_KEY comes from the environment, never a command-line argument — an argument is visible to every process on the machine. Over HTTP instead of stdio, the same key rides Authorization: Bearer; see brainy serve's "Minted keys" section for a short-lived, per-agent credential instead of one durable shared secret.
Choose a model — the five configurations
Brainy calls no language model unless you configured one, in every environment. find, get, add, relate, memory's recall/remember — the entire read/write/graph surface — work with nothing set at all. Only the jobs that genuinely need a model (classify, summarize, extract, judge, imagine, compose, rewrite) need one of the five below; without one, each of those refuses by name, naming the exact line to add.
# | Configuration | The one thing you set |
|---|---|---|
1 | Your own OpenAI-compatible endpoint |
|
2 | Anthropic |
|
3 | Gemini |
|
4 | A local model you already run | nothing — or |
5 | Soulcraft's hosted |
|
# 1 — your own OpenAI-compatible endpoint (OpenAI, Together, Groq, …)
export OPENAI_API_KEY=sk-…
# 2 — Anthropic
export ANTHROPIC_API_KEY=sk-ant-…
# 3 — Gemini
export GEMINI_API_KEY=…
# 4 — a local model you already run (Ollama :11434, LM Studio :1234, vLLM :8000) —
# autodetected on 127.0.0.1/::1 with NOTHING set; name a private address explicitly:
export BRAINY_LANGUAGE_ENDPOINT=http://127.0.0.1:11434/v1
# 5 — Soulcraft's hosted anima
export BRAINY_ANIMA_KEY=sc_live_your_key_hereBASHTwo vendor keys present with no BRAINY_LANGUAGE_PROVIDER to say which one is a refusal at startup, never a silent pick — see Language providers for the full precedence order, the sovereignty rule, and every refusal by name.
The first tools an agent calls
Real calls and real answer shapes, taken straight from this bridge's own door signatures (src/mcp/memoryTools.ts, src/accords/api.ts) — not approximated.
1. accord_inbox — first, every session. One call answers what the board asks of you, in protocol order.
// call
{ "participant": "your-agent-slug", "limit": 20, "awaitingOnly": true }
// answer shape (accords.Inbox)
{
"mandate": { "id": "…", "kind": "…", "fields": { /* … */ } },
"decisions": [ /* AccordRow[] */ ],
"actions": [ /* AccordRow[] */ ],
"threads": [ /* AccordRow[] */ ],
"totals": { "decisions": 0, "actions": 0, "threads": 0 }
}JSONC2. memory_search — before answering. The shared name: your client already speaks it if it has talked to another memory surface, and Brainy answers it here. Reach for memory_recall instead when you want this engine's own richer parameters — scope, layers, asOf — in the same call; it is the same read, with more dials.
// call
{ "query": "what did we decide about the agent docs page", "budgetTokens": 2000 }
// answer shape (ContextAnswer<T>)
{
"items": [ { "id": "…", "text": "…" } ],
"plan": { "slices": [ /* … */ ], "unit": "tokens", "budgetChars": 0, "usedChars": 0 }
}JSONC3. memory_brief — before answering, when the answer must already fit a budget. The same door as memory_context, under the name a client already reaching for a briefing looks for. In the TypeScript API it is brain.memory.context(query, params): budget in chars or tokens, scope by pathPrefix or world (a working brain answers only from its own workspace), thread for a conversation's last turns, and layers — pass ['abstraction'] for a digest-only answer. A digest is an abstraction-layer row your service writes (memory.remember with the abstraction subtype, then memory.update of each absorbed episode's processing.abstractedIntoMemoryId); the abstraction slice returns that one paragraph instead of the episodes it absorbed, while the tail keeps the last turns verbatim.
// call
{ "subject": "the docs-for-agents leg" }
// answer shape (ContextAnswer<T>)
{
"items": [ { "id": "…", "text": "…" } ],
"plan": { "slices": [ /* … */ ], "unit": "tokens", "budgetChars": 0, "usedChars": 0 }
}JSONC4. memory_turn — after each exchange. Idempotent on (conversationId, turn); the identical turn replayed is a no-op.
// call
{ "conversationId": "conv-042", "turn": 3, "speaker": "agent", "gist": "Answered with the five model configs and the first five tools." }
// answer shape (MemoryTurnReceipt)
{ "recorded": true, "id": "…", "measuredTokens": 24 }JSONC5. accord_post_round — to say something on a thread you are party to. Name afterRound with the last position you actually read, and clear your own flag with clearsMyAction when your part is done.
// call
{ "thread": "thread:EXAMPLE", "actor": "your-agent-slug", "text": "Draft is up.", "kind": "note", "clearsMyAction": true }
// answer shape (RoundReceipt)
{ "id": "thread:EXAMPLE", "record": "thread:EXAMPLE", "seq": 4, "total": 5, "absorbed": false, "generation": "1043" }JSONCactor is the current spelling of "who is speaking" everywhere in this API (actor · id · text · body · title · kind · reason · resolution · via · generation · related · schedule · query · where · limit · scope — the one vocabulary, door-names 12.6). The older spellings below are still read, through 12.x, as an alias of the field beside them — never a second, disagreeing field:
Older spelling | Current spelling | Where |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
What refuses by name, and why
Nothing here answers emptily. Every refusal names itself and the cure.
The language seam is unconfigured. A generative job with no provider configured refuses
LanguageProviderUnavailableError(or, before any model is even reached, the startup-time "no language provider configured" message naming every env var at once). Cure: set one of the five configurations above.A write, before native execution reaches it.
add,update,relate,transactand the eight accords writes (postRound,decide,resolve, …) are moving into the engine's core and are not there yet onbrainy-serve; each refusesNotYetNativeError, naming the door and the release it lands in. The bridge this page describes (npx brainy mcptoday) already calls the real in-process engine for these, so this refusal is abrainy-serve-only fact, not a bridge one — see MCP's "The bridge" section for which is which.A policy-refused write. Every accords
tools/callpasses one deterministic input check before dispatch.accord_decidewith zero or more than one option markedrecommendedrefusesRecommendationErrorbefore the call ever reaches the store — never a store read, never a partial write.
The subagent handoff
Brainy's tools exist to end rogue agents. An agent goes rogue when its context window floods and it forgets the goals, rules and restrictions it was given — which is a certainty, not a risk, for any long errand. So a parent never hands rules in a prompt. It files them, and the engine enforces them.
The ceremony, in the order the calls are made:
# | Who | Call | What it establishes |
|---|---|---|---|
1 | parent |
| The errand exists as a record, not a message |
2 | parent |
| The leash is a row before the subagent exists |
3 | subagent |
| It is handed the mandate and its own action, from the board |
4 | subagent |
| The |
5 | subagent | any write — |
|
6 | subagent |
| The report is a round on the record, not a message that can be lost |
7 | parent |
| What the rules actually stopped, with actor, door, rule and digest |
Two properties follow, and they are the whole point:
A subagent that loses its window loses nothing that binds it. The rules are rows on the store; step 4 hands them back on the next composition; step 5 enforces them whether or not step 4 ever ran.
A parent–subagent disagreement is an accord thread, not a lost message. Both sides post rounds on the same action; neither side's account of what was asked depends on a context window that has since been compacted.
The one line that makes the harness refuse a forbidden tool
Rules can name tools brainy does not serve. One hook line puts the same evaluator in front of them:
{ "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "command",
"command": "brainy policy check --brain $BRAINY_STORE --actor $CLAUDE_AGENT_SLUG --tool \"$CLAUDE_TOOL_NAME\" --args-json -" } ] } ] } }JSONExit 2 blocks the tool and shows the agent the rule that stopped it. The full design — the tiers, the lock, the audit and the gateway that will front other MCP servers — is The policy door.
Where a hosted brain lives
brainy.soulcraft.com is the published name for Soulcraft's hosted brain. The engine behind it lands on its own schedule; what is true today is the address is the contract — point a client at it the same way you would any other brainy serve deployment, over the same MCP, HTTP, gRPC or WebSocket doors this whole page describes, once it answers there.
Three environments, one table
Environment | Language provider | Network |
|---|---|---|
Local machine | None, or a local model on | Whatever the machine allows |
A server you run | Any of the five configurations above, set once for the process | Outbound to the vendor(s) you configured |
An enterprise, outbound-off | A private-address local model only ( |
|
Full detail on the outbound gate, the allowlist, and every refusal it can raise: Language providers.