Reading from your brain

Two ways to read. Ask for exact things and get bytes back, or hand the server a task and let a reader agent assemble what you need. Both answer only at your key's tier, and neither will tell you what it is hiding.

The read endpoints

Six endpoints that do exactly what they say and nothing clever. Every one of them resolves your tier, serves from that tier's snapshot, and logs what it handed over.

EndpointWhat you get
get-indexThe index of everything visible to you, generated per tier.
list-notesEntities, filterable by kind, topic, project and status. Defaults to current, so superseded notes stay out of your way.
get-noteThe full markdown of one entity.
get-lensOne lens: a named retrieval scope.
get-identityYour identity files. The ones that let an agent write as you rather than about you.
get-rawAn archived source. A raw/ file inherits the visibility of the notes that link to it.

There is also ping, which is the fastest way to find out what tier your key actually has:

POST /api/v1/ping
Authorization: Bearer <your-key>

{"pong": true, "service": "my-brain-web-app", "tier": "agents-only"}

Same brain, two keys, one question. An agents-only key lists 7 entities. A public key lists 2. Nothing in the agent decides that.

What a tier boundary feels like from below

The interesting question is not whether a low tier is refused. It is whether being refused tells you something.

Asking a public key for a note that really exists one tier up, and for a note that does not exist at all:

# exists, one tier above this key
{"error": {"code": "input_validation_error",
           "message": "unknown entity: take-2026-08-docs-from-real-sessions"}}

# does not exist anywhere
{"error": {"code": "input_validation_error",
           "message": "unknown entity: take-2026-08-this-does-not-exist"}}

Same status, same wording. There is no "exists but you may not have it", because that sentence is itself a disclosure. If you can probe for which ids come back differently, you can map a brain you cannot read.

This works because each tier is a materialized snapshot, built when your brain is indexed. The read layer is not filtering a full corpus per request and hoping the filter is right. It is reading a smaller corpus that never contained the note.

assemble-context: hand it a task, not a query

The endpoints above are for when you know what you want. This one is for when you know what you are about to do.

You send a task. A reader agent goes into your brain at your tier, picks what matters, and returns a context pack with the verbatim quotes preserved. It is not a search: "write a reply to this comment about testing" is a better input than "testing".

POST /api/v1/assemble-context

{"task": "Write a short paragraph in the owner's voice about why
          documentation should be written from real sessions."}

What comes back:

{"tier": "agents-only",
 "entity_ids_used": ["take-2026-08-docs-from-real-sessions"],
 "gaps": ["identity/core.md, identity/voice.md, and identity/beliefs.md
           are all unfilled template placeholders ..."],
 "tokens": {"input": 8, "output": 1648},
 "model": "claude-haiku-4-5-20251001, claude-sonnet-5",
 "duration_ms": 23635}

Two fields there earn their place. entity_ids_used means you can audit any answer back to the notes behind it. gaps is the agent telling you what it wanted and did not find. On that call it reported the identity files were still empty scaffolding, which is a real gap and a useful nudge.

You can pass a lens name to narrow the scope to a subject you have already defined.

This one is slow, and the docs say so. The endpoint documents 5 to 30 seconds. On a tiny brain, two calls took 18.7 and 24.6 seconds. It runs a real agent, so budget for it, do not put it in a tight loop, and do not retry it automatically. It is never retried for you.

Pointing Claude Code at it

Mint a key in the ops UI under API keys, then drop an .mcp.json next to your project:

{
  "mcpServers": {
    "brain": {
      "type": "http",
      "url": "https://your-domain/mcp/",
      "headers": { "Authorization": "Bearer <your-key>" }
    }
  }
}

That is the whole setup, and it is the same shape for any MCP client that speaks streamable HTTP: a URL ending in /mcp/ and a bearer token. Every read endpoint on this page shows up as a tool, and so does propose-feed if your key is agents-only or above.

One thing worth knowing if you run agents that already have other MCP servers configured: a nested run can inherit them. When it matters that an agent reads this brain and nothing else, pass --strict-mcp-config. It is easy to miss, because a second brain connector sitting alongside the one you meant to test answers perfectly well.

Consumer keys

Mint one key per agent or per project, not one key you paste everywhere. Each carries a name, a tier, and a rate limit per minute.

  • The key is shown once. Copy it then. If you lose it, rotate, which mints a replacement and kills the old one immediately.
  • A key with no consumer profile reads as public. The failure direction is least privilege: a misconfigured key sees less, never more.
  • agents-only is the right default for your own tools. It reads your notes and your identity files and never your private ones. Keep private for something you run and watch.
  • Revoking is immediate. Every call using that key fails from the next request.

Every read is logged with the entity ids it served, so who read what, and when is a question your instance can answer.

Where to go next