SmartBrain

Turning Claude Code into a pure LLM endpoint

September 7, 2026 · the SmartBrain project

SmartBrain is a self-hosted assistant that runs on local models by default. We wanted an optional extra: use the Claude subscription many people already pay for as a plain model backend — through the claude CLI, so there is no API key and no per-token bill. That means driving a full coding agent as if it were a bare model. Locking it down took real work, and the defaults surprised us. Here is what leaked, how we found it, and the exact recipe that seals it — all of it enforced by tests in our suite, not just promised.

Surprise #1: the obvious tool-blocking flag blocks nothing

Claude Code can read files, run shell commands, and browse. For a chat backend all of that must be off — the assistant embedding it has its own tools behind its own approval gates. The flag that looks right is --disallowedTools "*". We tested it by asking the model to run echo hi with its Bash tool:

{"type":"tool_use","name":"Bash","input":{"command":"echo hi"}}
{"type":"tool_result","content":"hi","is_error":false}

It ran. The wildcard is not a tool name, so it matches nothing, and there is no error telling you so. What actually works is defining a custom agent whose tool set is empty and selecting it for the session. The init event then reports "tools": [], and the model itself confirms it has no Bash access when asked to use it.

Surprise #2: your global CLAUDE.md rides into every session

We asked the contained session what personal context it could see. The answer, verbatim from the default configuration: the user's private ~/.claude/CLAUDE.md, quoted back with its path. A chat backend must not see the operator's personal instructions file. The fix is --setting-sources "" — load no user or project settings at all. We A/B-tested the same question with and without the flag: with it, the model reports no file access and no CLAUDE.md. What still reaches the model afterwards: the current date, and the email address of the signed-in Claude account — the account the chats already go to.

Surprise #3: argv is public

Every flag you pass is visible to any process on the machine via ps. So nothing conversation-derived can go on the command line: our agent definition on argv is a static constant, and the system prompt, tool descriptions, and transcript all travel over stdin. Our test asserts the argv is byte-identical across turns.

The recipe

claude -p --verbose \
  --output-format stream-json --include-partial-messages \
  --no-session-persistence \
  --setting-sources "" \
  --strict-mcp-config \
  --model sonnet \
  --agents '{"backend":{"description":"chat backend",
             "prompt":"<static pointer to stdin>","tools":[]}}' \
  --agent backend

Three environment details round it out:

Process hygiene, learned the hard way

An adversarial review of our first implementation found two ways it could wedge: stderr piped but never drained (the CLI is chatty under --verbose; a full pipe stalls it mid-answer), and a deadline that was only checked when output arrived — a hung CLI would block the reader forever. The fixes: merge stderr into stdout and filter, feed stdin from a helper thread, enforce the deadline with a watchdog timer that kills the child's whole process group (start_new_session + killpg — a helper process holding the pipe open survives a plain kill), and always reap.

Note on this cloud option: chats on this path go to Anthropic under the user's own sign-in. SmartBrain marks the option in red, keeps fully-local models the default, refuses to serve these models until the user explicitly connects them, and disconnecting actually stops the traffic — including for scheduled jobs that still name the model.

The payoff

The CLI becomes a well-behaved model backend: streaming tokens, tool use via a plain-text protocol our agent loop already speaks, token counts and API-equivalent cost reported per call (the CLI prices itself — no rate table to go stale), and the user's plan-window status straight from its rate-limit events. Subscription in, model out.

The full implementation — containment tests included — is open source: SmartBrain_3000, with the user-facing docs in Connect a model. Verified against Claude Code 2.1.x; flags move fast in this ecosystem — test, don't trust, including us.