Brainy Brainy
Docs Brainy

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 binaryBASH

npx 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": "…" }
    }
  }
}JSON

BRAINY_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

OPENAI_API_KEY=…

2

Anthropic

ANTHROPIC_API_KEY=sk-ant-…

3

Gemini

GEMINI_API_KEY=…

4

A local model you already run

nothing — or BRAINY_LANGUAGE_ENDPOINT=http://host:port/v1

5

Soulcraft's hosted anima

BRAINY_ANIMA_KEY=sc_live_…

# 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_hereBASH

Two 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 }
}JSONC

2. 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 }
}JSONC

3. 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 }
}JSONC

4. 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 }JSONC

5. 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" }JSONC

actor 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

author

actor

postRound, postUpdate

participant

actor

vote, resolve, escalate

toParticipant (inside asks[])

actor

file (thread filing)

summary

title

file (action filing)

record (on RoundReceipt)

id

postRound, postUpdate's answer

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, transact and the eight accords writes (postRound, decide, resolve, …) are moving into the engine's core and are not there yet on brainy-serve; each refuses NotYetNativeError, naming the door and the release it lands in. The bridge this page describes (npx brainy mcp today) already calls the real in-process engine for these, so this refusal is a brainy-serve-only fact, not a bridge one — see MCP's "The bridge" section for which is which.

  • A policy-refused write. Every accords tools/call passes one deterministic input check before dispatch. accord_decide with zero or more than one option marked recommended refuses RecommendationError before 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

accord_file — kind action, with the goal, the scope, the rule ids it must obey, and the decision that authorised the work

The errand exists as a record, not a message

2

parent

policy_declare — tier 1, subject = the subagent's slug, doors it may not use, effect: refuse

The leash is a row before the subagent exists

3

subagent

accord_inbox — its FIRST call, always

It is handed the mandate and its own action, from the board

4

subagent

memory_brief (memory.context()) — every turn

The mandate slice comes back FIRST and is never budget-trimmed

5

subagent

any write — add, memory_write, accord_post_round, vfs writeFile

policy.check runs INSIDE the door; a refusal names the rule and its sentence

6

subagent

accord_post_round on its action

The report is a round on the record, not a message that can be lost

7

parent

policy_audit

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 -" } ] } ] } }JSON

Exit 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 127.0.0.1/::1 — autodetected, zero config

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 (BRAINY_LANGUAGE_ENDPOINT on 10.0.0.0/8, 172.16.0.0/12, 192.168/16, or loopback)

BRAINY_LANGUAGE_OUTBOUND=off refuses any non-private endpoint before it is ever contacted; BRAINY_LANGUAGE_ALLOWED_ENDPOINTS names an explicit allowlist independent of that gate

Full detail on the outbound gate, the allowlist, and every refusal it can raise: Language providers.