How a downstream agent should talk to modusbrain
This guide is for authors of downstream agents (hermes, openclaw, future forks) that need to call modusbrain operations from their own runtime. Reading this first will save you a debugging cycle: modusbrain has two distinct surfaces, and which one you pick depends on the operation.The two surfaces
Surface 1 — MCP ops over HTTP (thin-client + OAuth)
Use for any operation that has an MCP equivalent:search, query,
put_page, get_page, find_experts, find_orphans, find_anomalies,
get_recent_salience, find_trajectory, and so on. The canonical list is
the set of ops in src/core/operations.ts whose localOnly flag is unset
(or false).
Setup
The host runs modusbrain as a long-lived HTTP server:/mcp with a bearer token from client_credentials
grant. Secrets stay in the modusbrain serve process; the agent never sees
DATABASE_URL or API keys.
Thin-client mode (modusbrain init --mcp-only) gives the agent the same
client-credentials wiring, plus the modusbrain CLI itself routes MCP-eligible
commands through the configured remote MCP. The agent can call
modusbrain search / modusbrain query directly and the CLI does the OAuth dance.
Why this is preferred for MCP ops
- Secrets never leave the server process.
- OAuth scopes give you
read,write,adminseparation — agent only gets what it needs. - Source-scoped tokens (
--source dept-xonregister-client) confine the agent to a specific source within a federated brain. - One audit surface (
mcp_request_log) covers every op call uniformly.
Surface 2 — localOnly admin ops via shell-job inherit:
Some operations are flagged localOnly: true in src/core/operations.ts and
are refused in thin-client mode at src/cli.ts:isThinClient. The full
list (as of v0.36.5.0) includes:
sync(filesystem walks need local FS access)embed(orchestrates the embed pipeline)extract(walks markdown files)dream(synthesis cycle)doctor(filesystem hygiene checks)autopilot(background daemon orchestration)init(creates~/.modusbrain/)secrets(config management)
modusbrain as a CLI subprocess. The recommended pattern is to submit the
subprocess as a shell job to the modusbrain Minions worker so retry / backoff /
DLQ / audit trail all come for free.
Setup
inherit: ["database_url"] field tells the worker to look up
database_url from its loadConfig() and inject the value into the child
env as MODUSBRAIN_DATABASE_URL. The DB row in minion_jobs.data carries the
names only — inherit: ["database_url"] — never the value. See
minions-shell-jobs.md#secrets for the
full validation rules and error catalog.
Why this is preferred over writing secrets into env: per-job
- Pre-v0.36.5.0 callers passed
env: { MODUSBRAIN_DATABASE_URL: "postgresql://..." }per job. The URL landed plaintext inminion_jobs.dataand the shell-audit JSONL. Anyone with brain-DB read access (or a brain dump, or a shared brain via mounts) saw the URL. As of v0.36.5.0, this is rejected at pre-enqueue validation. The error message namesinherit: ["database_url"]as the replacement.
Worker setup (one-time, per host)
The agent’s host needs a worker that processes shell jobs:MODUSBRAIN_ALLOW_SHELL_JOBS=1 is the worker-side opt-in. Without it, shell jobs
sit in waiting indefinitely. Set it on the worker process env (or in your
deploy unit / launchd plist), not per-submission — submitter env is a weak
proxy for worker env.
Decision table
Recommended patterns
- Prefer
inherit:for secrets you don’t want in the row. Names land inminion_jobs.data; values resolve at child-spawn from the worker’s config. If a brain DB ever traverses a trust boundary, secrets stay out. - Free-form names.
inherit:accepts any snake_case config-key on your worker —database_url,anthropic_api_key,openai_api_key,voyage_api_key,groq_api_key,zeroentropy_api_key, or any custom field you stuff into~/.modusbrain/config.json. The agent picks what it needs. env:still works for non-secret values, or for cases where you WANT the value in the row (e.g. an opaque correlation token your audit flow needs to read back later). The validator doesn’t second-guess you.- Never try to route a
localOnlyop through thin-client MCP. It will fail withlocalOnly op refused in thin-client mode. Use shell-job +inherit:(for secrets) orenv:(for non-secrets).
Migration: from pre-v0.36.5.0
If your agent submits shell jobs that pass secrets viaenv::
database_url configured (either via
modusbrain config set database_url <value> or via MODUSBRAIN_DATABASE_URL /
DATABASE_URL env on the worker process). If the worker can’t resolve the
key, the validator rejects the job at submit time with a paste-ready hint.