Documentation

Quick start

BrainLLM needs a running TriliumNext instance and Bun 1.0 or later. It uses Bun's APIs directly and does not run on Node.

1. Run Trilium and create a token

Install TriliumNext on your computer or anywhere reachable over HTTP, then create a token under Options → ETAPI → Create token. Or skip the token and set TRILIUM_PASSWORD: BrainLLM creates one on first start and caches it.

2. Install

# from npm (needs bun on PATH)
npx -y brainllm

# or from source
git clone https://github.com/miisodev/BrainLLM
cd BrainLLM && bun install && bun run build

Client configuration

For Claude Desktop, Claude Code or any client that launches local servers:

{
  "mcpServers": {
    "BrainLLM": {
      "command": "npx",
      "args": ["-y", "brainllm"],
      "env": {
        "TRILIUM_BASE_URL": "http://localhost:8080",
        "TRILIUM_ETAPI_TOKEN": "your-token"
      }
    }
  }
}

For claude.ai, the Claude apps and Cowork, BrainLLM runs as a remote server instead: see Connect to Claude.

Bootstrap the brain

On a fresh Trilium, call bootstrap() once from your client, or run bun run init from a checkout. It builds the five-area tree, engraves each container's purpose and writes brainllm.json. It is safe to run at any time: it checks an existing root live and builds a new tree only on a confirmed miss.

Teach the model to operate it

The tools are terse single-word verbs; the working discipline lives in the bundled skill at skills/brainllm/. Copy it to ~/.claude/skills/brainllm for Claude Code and Cowork, or upload skills/brainllm.zip to claude.ai. Without it a model can call every tool correctly and still keep an untidy brain, because a correct call is not a correct practice.

Environment variables

VariableRequiredPurpose
TRILIUM_BASE_URLYesURL of your TriliumNext instance
TRILIUM_ETAPI_TOKENYes*ETAPI token (Trilium: Options → ETAPI → Create token)
TRILIUM_PASSWORD*Alternative to the token: BrainLLM creates one on first start and caches it
BRAINLLM_MODEcore (45 tools, default) or full (adds 33 raw Trilium tools, 78 in all)
BRAINLLM_TZIANA timezone for date stamps on hosted deploys, e.g. Africa/Johannesburg
BRAINLLM_DELETION_CATCHUP_DAYSDays your Trilium keeps deleted notes, when it differs from Trilium's default of 7
PORTSwitches to HTTP mode. Leave unset for local stdio
MCP_AUTH_TOKENStatic bearer token for HTTP mode; HTTP refuses to start without it or OAuth
BRAINLLM_OWNER_PASSWORDTurns on OAuth 2.1, which claude.ai and the Claude apps need. It is the password on the consent screen
BRAINLLM_OAUTH_SECRETOptional 32+ character signing secret; rotate it to invalidate every token
BRAINLLM_CONFIGAbsolute file path for brainllm.json on a persistent volume
BRAINLLM_PUBLIC_URLThe public origin, when a proxy rewrites Host
BRAINLLM_TRUST_PROXYTrust forwarded host and protocol headers, only behind a trusted proxy
BRAINLLM_ALLOW_UNAUTHENTICATED_HTTPTrusted networks only. Never on a public interface

Tool surface

45 tools by default: 35 verbs and 10 surface reads. BRAINLLM_MODE=full adds 33 raw Trilium tools, 78 in all. Every tool carries a human-readable title and is marked read-only, write or destructive, so clients can group their permission prompts; an unclassified tool is treated as a write, never a read.

GroupTools
Sessionstart · day · session · remarks · close · backup · health
Writingremember · diary · split
Reading and searchread · recall · domain · brain · assembly · template · consistency
Updatingrevise · resolve · withdraw · recover · label · forget
Graph and inspectionconnect · explore · graph · outline · inspect · diff
Attachmentsattach · detach
Maintenanceaddendum · maintain · claim · bootstrap
Surface readsmaster · llm · memory · knowledge · insights, each with a _recall skim twin

Kinds and statuses

Sixteen kinds: biography, goals, preferences, responsibilities, protocols, selfcorrection, diary, session, thread, threadEntry, user, domain, information, sources, log and claim. Five statuses: active, dormant, resolved, superseded and eternal. Only threads age, and an eternal thread never does.

Session protocol

START   start()                    the first BrainLLM call of a session
        day()                      when start() reports a new day
DURING  remember · revise · recall · domain · connect · ...
END     session() → addendum() → maintain() → remarks() → diary() → close()

close() refuses until the closing steps have run, with session → remarks → diary in that order. diary() and close() take an identity line, "LLM · environment · agent/mode". The gate's state lives on the day's session note, so it survives a restart. session() lists the notes the session changed, so a figure corrected in one note can be checked everywhere it is recorded.

Editing large notes

  • revise(noteId, section="Heading") replaces one section; mode="before", "after", "prepend" and "remove" act around it.
  • revise(noteId, find="a few words", within="tr") replaces, inserts beside or removes the table row containing them. Inline formatting in the stored text doesn't stop a match.
  • revise(domain="Name", find=, body=) previews a find and replace across a whole domain; add dryRun=false to write it.
  • Reads take section=; records take block="14:05" or block="-1" for one addendum; read(ids=[...], text=true) returns readable text for orientation.
  • diff(since="today") shows every change made today, with table edits reported cell by cell.

Transports

ModeWhenUse for
stdioPORT unsetClaude Desktop and Claude Code, which start BrainLLM themselves
HTTPPORT setDocker, Railway or any container host. Streamable HTTP at /mcp, legacy SSE at /sse, and /health, all behind the same authentication

On a container host, mount a volume and set BRAINLLM_CONFIG to a file inside it, or discovery reruns on every cold start.

OAuth

claude.ai and the Claude apps connect with OAuth only; they have no field for a bearer token. BrainLLM is its own OAuth 2.1 authorization server, with PKCE, Client ID Metadata Documents and dynamic registration at /register. Set BRAINLLM_OWNER_PASSWORD to turn it on; unset, the OAuth surface does not exist. The consent screen names the requesting client and the address it returns to, and warns when that address is your own computer. Refresh tokens rotate on use and are stored only as hashes.

Health and backups

close() backs up into one of seven weekday slots. backup(name) keeps a named milestone, which stays on Trilium's volume until deleted there. health() reports Trilium's version, an estimate of the database's size, the heaviest notes and their revision counts, and the named backups taken through BrainLLM, and flags growth before the disk fills.

Troubleshooting

SymptomFix
Tools time outTrilium is unreachable. Check TRILIUM_BASE_URL. Every call is bounded at 30 seconds, so a hang fails one call rather than the session
start() returns uninitializedRun bootstrap() once
claude.ai says it couldn't reach the serverOAuth discovery failed, which is almost never the network. Check BRAINLLM_OWNER_PASSWORD is set and that the URL you entered matches the server's, trailing slash included
Every call fails after an hourToken refresh failed because the signing secret changed. Point BRAINLLM_CONFIG at a persistent volume
ENOENT on a hosted startBRAINLLM_CONFIG names a directory; it must be a file path
revise(find=) replaced nothingRead the hint: it names the cause and shows the stored text nearby. For a table row, anchor on a few words with within="tr"
A result ends with a note that it was cutIt passed about 140,000 characters. Narrow the read with section=, block=, limit= or fewer ids=

Still stuck? See Support.