Docs · agents

Your agent, with a brain.

Every coding agent takes in outside context its own way. Some run a command when a session opens, some on every message, some only talk MCP. For each agent this page says what the brain can get through and what to type to set it up. We tested every one on a clean machine, on the release named in its section.

Not covered yet #

We have run every agent below with a brain wired into it. We haven't tried these yet, so we can't tell you what gets through in them:

That only means we haven't tested them. If one of them speaks MCP over stdio, start with the MCP page.

What a brain needs from an agent #

There are four ways in and an agent can offer any mix of them. The brief at session start matters most: the model has it before it decides anything. Recall on each message brings up facts you'd never think to search for. The end-of-session hook records what the session did and whether it saved any of it. MCP lets the model read and write the brain whenever it chooses to.

ChannelWhat it carriesWho decides
Session startThe brief: guardrails, preferences, pinned facts, the working treeThe hook. Always.
Every messageMemories that match what you typedThe hook. Always.
Session endThe session's footprint, for the next briefThe hook. Always.
MCPEvery command, as a toolThe model, when it thinks of it

A hook runs every time, whether the model remembers or not. MCP runs only when the model decides to call it: most of the time, never all of the time. So init wires hooks wherever an agent has them and MCP fills the gaps.

Claude Code #

We designed the hooks around Claude Code and we build ns-brain in it, with a brain wired exactly as this section describes. Claude Code carries every channel.

ChannelIn Claude CodeHow
Session startWorksSessionStart runs ns-brain wakeup
Every messageWorksUserPromptSubmit runs ns-brain hook-recall on what you typed
Session endWorksStop at the end of each turn, SessionEnd when you quit
During a turnWorksPostToolUse runs ns-brain hook-tool, so a message sent to a busy session lands on its next tool call
MCP read and writeWorks.mcp.json or claude mcp add

Install #

$ curl -fsSL https://brain.fightclub.pro/install.sh | sh
$ cd ~/code/yourproject
$ ns-brain init --agent claude

At a terminal, plain ns-brain init asks which agent to wire and Claude Code is the first choice on the list. The last choice, "I will do it myself", wires nothing and prints the block each agent needs so you can add it by hand. If there's no terminal to ask, plain init wires every agent whose config directory it finds in the project or in your home directory. Pick Claude Code and five hooks go into .claude/settings.json, beside whatever the file already holds:

# .claude/settings.json, the hooks part
"hooks": {
  "SessionStart":     [{ "hooks": [{ "type": "command", "command": "BRAIN_HOME=\"$CLAUDE_PROJECT_DIR/brain\" ns-brain wakeup" }] }],
  "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "BRAIN_HOME=\"$CLAUDE_PROJECT_DIR/brain\" ns-brain hook-recall" }] }],
  "PostToolUse":      [{ "hooks": [{ "type": "command", "command": "BRAIN_HOME=\"$CLAUDE_PROJECT_DIR/brain\" ns-brain hook-tool" }] }],
  "Stop":             [{ "hooks": [{ "type": "command", "command": "BRAIN_HOME=\"$CLAUDE_PROJECT_DIR/brain\" ns-brain hook-stop" }] }],
  "SessionEnd":       [{ "hooks": [{ "type": "command", "command": "BRAIN_HOME=\"$CLAUDE_PROJECT_DIR/brain\" ns-brain hook-end" }] }]
}

The hooks name the brain through $CLAUDE_PROJECT_DIR, which Claude Code sets, so there's no absolute path in the file. Commit it only if everybody working on the repository has ns-brain installed: for anyone who doesn't, every session starts with a failing hook. --no-auto-recall leaves out UserPromptSubmit. That also turns off the prompt capture the Install page describes.

init also writes a few slash commands into .claude/commands/: /who, /msg, /rdo, /dnd, /inbox, /accept and /reject, for seeing and messaging other live sessions. It doesn't touch CLAUDE.md.

The hooks already bring memories in on every message, so MCP is optional. Add it when you want the model to store things itself:

$ claude mcp add brain -- ns-brain mcp

Claude Code starts the server in the project, so it finds the brain with no path. On a shared store add -e BRAIN_PROJECT=yourproject.

Check it worked #

Save a fact with low importance. Those stay out of the brief, so if Claude knows the answer it got it from recall on your message:

$ ns-brain remember "Mascot colour for the release banner" --type knowledge --importance 1 \
      --body "The release banner mascot is painted magenta."
$ claude -p "Without running any tool or reading any file: what colour is the release banner mascot painted?"
magenta

To check the brief rather than recall, pin a fact the way the Cursor test does and ask for it the same way. If Claude says it doesn't know, run /hooks in a session to see what Claude Code loaded. Then open a new session instead of resuming an old one. Troubleshooting a missing brief covers the rest.

Cursor #

Tested with the Cursor CLI (agent, release 2026.09.23) and ns-brain 26101601. Cursor's documentation says the IDE reads the same two files. We haven't tested the IDE yet and this page will say so until we have.

ChannelIn CursorHow
Session startWorkssessionStart hook, brief returned as additional_context
Every messageNot availableCursor's prompt hook can block a message but can't add to it. Use MCP.
Session endWorkssessionEnd hook. stop is wired too but never fires from the CLI.
MCP read and writeWorks.cursor/mcp.json, stdio

Install #

$ curl -fsSL https://brain.fightclub.pro/install.sh | sh
$ cd ~/code/yourproject
$ ns-brain init --agent cursor

That writes brain/ with its database and docs, gitignores the database and wires three events in .cursor/hooks.json. Anything already in that file stays where it was. Run it again and nothing changes.

# .cursor/hooks.json, as init writes it in ~/code/yourproject
{
  "version": 1,
  "hooks": {
    "sessionStart": [{ "command": "/home/you/code/yourproject/brain/autoexec.sh wakeup --emit cursor" }],
    "stop":         [{ "command": "/home/you/code/yourproject/brain/autoexec.sh hook-stop" }],
    "sessionEnd":   [{ "command": "/home/you/code/yourproject/brain/autoexec.sh hook-stop" },
                     { "command": "/home/you/code/yourproject/brain/autoexec.sh hook-end" }]
  }
}

The paths are absolute, so the file belongs to your checkout. Keep it out of the repository and let each person run init in their own. autoexec.sh finds the brain from its own location, so the hook works from whatever directory Cursor runs it in.

Recall through MCP #

Cursor has no prompt hook, so recall only happens when the model asks for it. Give it the server:

# .cursor/mcp.json
{
  "mcpServers": {
    "brain": {
      "type": "stdio",
      "command": "ns-brain",
      "args": ["mcp"],
      "env": { "BRAIN_HOME": "${workspaceFolder}/brain" }
    }
  }
}

Cursor expands ${workspaceFolder} itself, so the file has no absolute path in it and can go in the repository if everybody working there has a brain. If anybody doesn't, keep it out: for them it's a server that fails in every session. On a shared store add "BRAIN_PROJECT": "yourproject" to env. The MCP page explains why the server refuses to guess.

From the CLI, approve the server the first time with --approve-mcps:

$ agent -p --approve-mcps "what does the brain say about the staging database?"

Check it worked #

Store something the agent couldn't know any other way, pin it so it's in the brief and ask for it in a fresh session without letting it read anything:

$ ns-brain remember "Staging database port is 50746" --type infra --pin \
      --body "The staging Postgres listens on 50746, not 5432."
$ agent -p "Without running any tool or reading any file: what port does staging Postgres use?"
50746

If it says it doesn't know, the hook didn't fire. Run ./brain/autoexec.sh wakeup --emit cursor by hand: it should print one JSON object with the brief in additional_context. Troubleshooting covers the usual reasons it doesn't.

Cursor and Claude Code together #

Cursor also runs the hooks in .claude/settings.json. In a repository where some people use Claude Code, ns-brain init --agent claude,cursor wires both and in Cursor both session-start hooks fire. From 26101601 the Claude hook notices that Cursor started it and that Cursor's own hook is wired. So it stays quiet and the brief arrives once. Claude Code itself is unaffected, including a Claude Code session started from Cursor's terminal.

What Cursor cannot carry #

Gemini CLI #

Tested with Gemini CLI 0.64.0 and ns-brain 26101602. Gemini carries every channel a brain needs, so nothing has to go through MCP that a hook can do instead.

ChannelIn GeminiHow
Session startWorksSessionStart hook
Every messageWorksBeforeAgent hook runs recall on what you typed
Session endWorksAfterAgent at the end of each turn, SessionEnd when you quit
MCP read and writeWorksmcpServers in .gemini/settings.json

We ran Gemini with an API key. Signing in with the personal Google account we tried was refused, so we didn't test sign-in. To use a key, paste it when Gemini first asks or export GEMINI_API_KEY.

Install #

$ curl -fsSL https://brain.fightclub.pro/install.sh | sh
$ cd ~/code/yourproject
$ ns-brain init --agent gemini

Four hooks go into .gemini/settings.json, beside whatever the file already holds:

# .gemini/settings.json, the hooks part
"hooks": {
  "SessionStart": [{ "hooks": [{ "type": "command", "command": "/home/you/code/yourproject/brain/autoexec.sh wakeup --emit gemini" }] }],
  "BeforeAgent":  [{ "hooks": [{ "type": "command", "command": "/home/you/code/yourproject/brain/autoexec.sh hook-recall --emit gemini" }] }],
  "AfterAgent":   [{ "hooks": [{ "type": "command", "command": "/home/you/code/yourproject/brain/autoexec.sh hook-stop --emit gemini" }] }],
  "SessionEnd":   [{ "hooks": [{ "type": "command", "command": "/home/you/code/yourproject/brain/autoexec.sh hook-end" }] }]
}

Gemini only reads a hook's output as JSON, which is what --emit gemini is for. It also stuffs everything added so far into the prompt it passes to the next hook. ns-brain strips that out before it searches, so recall matches what you typed and not the brief.

MCP #

You don't need it here, since the hooks already bring memories in on every message. Add it when you want the model to store things itself:

# .gemini/settings.json, next to "hooks"
"mcpServers": {
  "brain": {
    "command": "ns-brain",
    "args": ["mcp"],
    "env": { "BRAIN_HOME": "/home/you/code/yourproject/brain" },
    "trust": true
  }
}

trust lets the model call the brain's tools without a confirmation each time. Leave it out and Gemini asks first. On a shared store add "BRAIN_PROJECT": "yourproject" as well.

Check it worked #

No pin this time. A low-importance memory stays out of the brief, so if Gemini knows the answer it got it from recall on your message:

$ ns-brain remember "Mascot colour for the release banner" --type knowledge --importance 1 \
      --body "The release banner mascot is painted magenta."
$ gemini -p "Without running any tool or reading any file: what colour is the release banner mascot painted?"
magenta

Things Gemini asks of you #

OpenCode #

Tested with OpenCode 1.19.2 and ns-brain 26101604. OpenCode doesn't run hook commands. It loads JavaScript plugins instead, so init writes one and every channel works through it.

ChannelIn OpenCodeHow
Session startWorksThe plugin adds the brief to the system prompt, once per session
Every messageWorkschat.message: recall on what you typed, sent with that turn
Session endWorkssession.idle, at the end of each turn
MCP read and writeWorksmcp in opencode.json

Install #

$ curl -fsSL https://brain.fightclub.pro/install.sh | sh
$ cd ~/code/yourproject
$ ns-brain init --agent opencode
  + OpenCode plugin -> /home/you/code/yourproject/.opencode/plugins/brain.js

OpenCode picks up anything in .opencode/plugins/ on its next start. Only a session somebody typed into gets the brief. The extra model call OpenCode makes to name a session doesn't. The plugin runs the brain's own autoexec.sh and hands it OpenCode's session id, so a session's start and end land on the same record. If a brain.js of your own is already there, init leaves it alone and prints the plugin for you to merge by hand. ns-brain uninstall removes the plugin, but only if init wrote it.

The plugin names the brain by absolute path, like the other agents' config files do. Keep it out of the repository and let each person run init.

MCP #

For the model to store memories itself, add the server to opencode.json in the project:

{
  "mcp": {
    "brain": {
      "type": "local",
      "command": ["ns-brain", "mcp"],
      "enabled": true,
      "environment": { "BRAIN_HOME": "/home/you/code/yourproject/brain" }
    }
  }
}

Without environment the server finds the brain from the directory OpenCode was started in, which is fine as long as that's always the project. On a shared store add "BRAIN_PROJECT": "yourproject" next to BRAIN_HOME.

Check it worked #

$ ns-brain remember "Mascot colour for the release banner" --type knowledge --importance 1 \
      --body "The release banner mascot is painted magenta."
$ opencode run "Without running any tool or reading any file: what colour is the release banner mascot painted?"
magenta

A low-importance memory isn't in the brief, so the answer can only have come from recall on the message. If OpenCode says it doesn't know, check that .opencode/plugins/brain.js exists and that OpenCode was restarted after init.

Kilo #

Tested with the Kilo CLI 7.8.12 and ns-brain 26101604. Kilo grew out of OpenCode and takes the same plugin, so everything in the OpenCode section applies, with Kilo's own paths.

ChannelIn KiloHow
Session startWorksThe plugin adds the brief to the system prompt, once per session
Every messageWorkschat.message: recall on what you typed
Session endWorkssession.idle, at the end of each turn
MCP read and writeWorksmcp in kilo.jsonc
$ cd ~/code/yourproject
$ ns-brain init --agent kilo
  + Kilo plugin -> /home/you/code/yourproject/.kilo/plugin/brain.js

Kilo reads .kilo/plugin/ (singular) and ignores .opencode/plugins/. So a repository where some people use OpenCode and some use Kilo can carry both and nobody is briefed twice. Plain ns-brain init finds Kilo through .kilo in the project, ~/.kilo or ~/.config/kilo. At a terminal it marks Kilo as found on this machine; with no terminal it wires Kilo without asking.

# kilo.jsonc, for MCP
{
  "mcp": {
    "brain": {
      "type": "local",
      "command": ["ns-brain", "mcp"],
      "enabled": true,
      "environment": { "BRAIN_HOME": "/home/you/code/yourproject/brain" }
    }
  }
}

Kilo rewrites kilo.jsonc into its own layout when it starts, which is harmless. If BRAIN_HOME points at nothing the server fails to start. The model gets no error: it's told the brain's tools don't exist. So if recall over MCP goes quiet, check the path first. To check the plugin, use the same low-importance memory as for OpenCode with kilo run.

Cline CLI #

Tested with the Cline CLI 3.0.72 and ns-brain 26101605. This is the one agent so far where hooks can't help. The CLI does run hook files, but in the background. It throws away what they print, so nothing a hook says reaches the model. The brain gets in through MCP and only when the model decides to ask.

ChannelIn Cline CLIHow
Session startNot availableTaskStart runs, but detached and its output is discarded
Every messageNot availableUserPromptSubmit is detached the same way
Session endNot wiredThe hook fires, but with no session start there's nothing to close
MCP read and writeWorks~/.cline/data/settings/cline_mcp_settings.json, one project at a time

So init writes nothing for Cline. Create the brain as usual and add the server by hand:

$ cd ~/code/yourproject && ns-brain init

# ~/.cline/data/settings/cline_mcp_settings.json
{
  "mcpServers": {
    "brain": {
      "command": "ns-brain",
      "args": ["mcp"],
      "env": { "BRAIN_HOME": "/home/you/code/yourproject/brain" },
      "disabled": false,
      "autoApprove": ["recall", "remember"]
    }
  }
}

Here BRAIN_HOME is required. The CLI starts every MCP server from one shared background process. That process sits in whichever project started it first, so a server that looks for its brain in the current directory finds the wrong one. In our test it did exactly that and answered from another project with no warning. From 26101605 the server refuses every call from Cline until BRAIN_HOME or BRAIN_PROJECT names the brain. The refusal says which file to edit. On a shared store use BRAIN_HOME. A project's name belongs to its own directory, so BRAIN_PROJECT alone is refused when the server sits in another project.

The file is per user, not per project. Cline doesn't expand ${workspaceFolder} in it, so it points at one brain. Moving to another project means editing the path. autoApprove stops Cline asking before each recall; leave it out if you'd rather approve them.

The Cline extension for VS Code is a separate product with its own hook rules, which we haven't tested yet.

GitHub Copilot CLI #

Tested with Copilot CLI 1.0.98 and ns-brain 26101606, on a model of our own through Copilot's bring-your-own-key settings. Copilot runs the hooks in .claude/settings.json and sends them Claude Code's own payloads, so it uses the same wiring as Claude Code. The difference is the reply. Copilot ignores plain text and reads only JSON. From 26101606 the brain tells the two agents apart and answers each in its own format.

ChannelIn Copilot CLIHow
Session startWorksSessionStart in .claude/settings.json
Every messageWorksUserPromptSubmit, recall on what you typed
Session endWorksStop and SessionEnd
MCP read and writeWorks~/.copilot/mcp-config.json, one entry for every project
$ cd ~/code/yourproject
$ ns-brain init --agent copilot
  + SessionStart hook -> BRAIN_HOME="$CLAUDE_PROJECT_DIR/brain" ns-brain wakeup (Copilot CLI reads this file)

That writes Claude Code's hook file even if nobody on the project uses Claude Code. That's the file Copilot reads. Using one file means a project used from both agents gets one brief in each and never two. Copilot also reads .github/hooks/; leave the brain out of there, or every session gets the brief twice. These hooks name the brain through $CLAUDE_PROJECT_DIR, which Copilot sets too, so there's no absolute path in the file. Commit it only if everybody working on the repository has ns-brain installed: for anyone who doesn't, every session starts with a failing hook.

A headless copilot -p run skips the repository's hooks unless you allow them. Set GITHUB_COPILOT_PROMPT_MODE_REPO_HOOKS=true in the environment of the script. COPILOT_ALLOW_ALL=true also works, but it allows a great deal more than hooks.

# ~/.copilot/mcp-config.json
{
  "mcpServers": {
    "brain": { "type": "local", "command": "ns-brain", "args": ["mcp"], "tools": ["*"] }
  }
}

Copilot starts the server in the project you're working in, so this one entry finds the right brain in every project with no BRAIN_HOME. On a shared store add "env": { "BRAIN_PROJECT": "yourproject" }, which ties the entry to that project.

$ ns-brain remember "Mascot colour for the release banner" --type knowledge --importance 1 \
      --body "The release banner mascot is painted magenta."
$ GITHUB_COPILOT_PROMPT_MODE_REPO_HOOKS=true copilot -p "Without running any tool or reading any file: what colour is the release banner mascot painted?"
magenta

We've only run Copilot headless. The interactive CLI and Copilot in VS Code are next.

Qwen Code #

Tested with Qwen Code 0.25.2 and ns-brain 26101607, on an OpenAI-compatible model. Qwen takes Claude Code's hook format and reads plain output, but from its own .qwen/settings.json; it doesn't look at .claude/.

ChannelIn Qwen CodeHow
Session startWorksSessionStart in .qwen/settings.json
Every messageWorksUserPromptSubmit, recall on what you typed
Session endWorksStop each turn, SessionEnd on quit
MCP read and writeWorksmcpServers in .qwen/settings.json
$ cd ~/code/yourproject
$ ns-brain init --agent qwen
  + Qwen Code SessionStart hook -> /home/you/code/yourproject/brain/autoexec.sh wakeup

For MCP, add the server beside the hooks. It starts in the project, so it needs no path:

# .qwen/settings.json, next to "hooks"
"mcpServers": {
  "brain": { "command": "ns-brain", "args": ["mcp"], "trust": true }
}
$ ns-brain remember "Mascot colour for the release banner" --type knowledge --importance 1 \
      --body "The release banner mascot is painted magenta."
$ qwen -p "Without running any tool or reading any file: what colour is the release banner mascot painted?"
The release banner mascot is painted magenta.

Qwen keeps its own memory for each project and it can learn to distrust what the brain gives it. Ours did. We asked the same question in the same folder with a new random value every run. Qwen wrote itself a note that the brain's block was a prompt injection and then answered UNKNOWN on purpose. It took facts that kept changing for no reason to get there. If Qwen ever starts ignoring the brain, look in ~/.qwen/projects/<your project>/memory/.

This is a known issue and the brain does nothing about it yet. What init wires for Qwen is the same as what other agents get.

Hermes Agent #

Tested with Hermes Agent 0.22.1 and ns-brain 26101608, on an OpenAI-compatible model. Hermes is more a personal agent than a project tool. Its hooks and MCP servers are set once for your user in ~/.hermes/config.yaml. They run from Hermes's own install directory, not from your project. So the hook itself has to name the brain and one Hermes setup reads one brain.

ChannelIn HermesHow
Session startWorksThe brief rides on the first pre_llm_call of a session
Every messageWorkspre_llm_call, recall on what you typed
Session endWorkson_session_end
MCP read and writeWorksmcp_servers in ~/.hermes/config.yaml, with BRAIN_HOME
$ cd ~/code/yourproject
$ ns-brain init --agent hermes

init won't edit ~/.hermes/config.yaml. It's one file for your whole user and it lives outside the project. Rewriting it to wire up one project isn't our call. It prints the hooks block instead, with this project's paths filled in:

# ~/.hermes/config.yaml, as init prints it
hooks:
  pre_llm_call:
    - {command: "/home/you/code/yourproject/brain/autoexec.sh hook-recall --emit hermes", timeout: 20}
  on_session_end:
    - {command: "/home/you/code/yourproject/brain/autoexec.sh hook-stop", timeout: 15}

init doesn't print the MCP server. For MCP, add this to the same file yourself:

# ~/.hermes/config.yaml, for MCP
mcp_servers:
  brain:
    command: ns-brain
    args: [mcp]
    env: {BRAIN_HOME: /home/you/code/yourproject/brain}

If hooks: is already in the file, merge the two entries into it. Don't add a second one: Hermes refuses a config with the same key twice. Hermes asks you to approve new hooks the first time they run (--accept-hooks does it for a script) and hermes hooks list shows what it has approved. If a gateway is already running, hermes gateway stop makes the next session read the new config.

Hermes tacks what a hook returns onto your own message and the model weighs it that way. Asked plainly, "what colour is the release banner mascot painted?", it answered from the brain every time. Asked "without running any tool or reading any file, what do you already know", it sometimes said it didn't know and called the memory unverified.

Continue CLI #

Tested with the Continue CLI (cn) 1.5.47 and ns-brain 26101610. cn reads Claude Code's hook file and Continue's own, then never runs either: the hook code is there and nothing calls it. So the brain reaches Continue through MCP only, when the model decides to ask.

ChannelIn Continue CLIHow
Session startNot availableHooks are loaded but never fired
Every messageNot availableSame
Session endNot availableSame
MCP read and writeWorksmcpServers in ~/.continue/config.yaml, one entry for every project
$ cd ~/code/yourproject && ns-brain init --agent continue

# ~/.continue/config.yaml, as init prints it
mcpServers:
  - name: brain
    type: stdio
    command: ns-brain
    args: [mcp]

Put it in the YAML config you run cn with. In our runs cn -p didn't load a server block from the workspace's .continue/mcpServers/. The server starts in the project, so this one entry finds the right brain wherever you run cn; on a shared store add env: {BRAIN_PROJECT: yourproject}. Asked to use the server when it wasn't loaded, the model ran ns-brain recall in the shell instead and got the right answer that way.

Aider #

Tested with Aider 0.86.2. Aider has no hooks and no MCP. It does have --read, which shows the model a file as read-only context. So the brief can go in as a file written just before Aider starts.

ChannelIn AiderHow
Session startWorks, with a wrapperThe brief, written at launch and passed with --read
Every messageNot availableNo hooks
Session endNot availableNo hooks
MCP read and writeNot availableNo MCP

init --agent has nothing to write for Aider. Create the brain with plain ns-brain init and put this in your shell profile:

# ~/.bashrc or ~/.zshrc
aider() {
  brief=$(mktemp "${TMPDIR:-/tmp}/brain-brief.XXXXXX") || return
  ns-brain wakeup > "$brief" 2>/dev/null
  command aider --read "$brief" "$@"
  rm -f "$brief"
}

The brief goes to a temporary file, not into the repository, because it holds the project's guardrails and preferences. The file is removed when Aider exits. Outside a project with a brain, wakeup prints nothing and Aider starts as usual.

You get the brief as it was when Aider started. Nothing comes in on later messages and nothing is saved unless you ask Aider to run ns-brain remember with /run. Aider sends read-only files with every message, so the brief costs its full size each time, about 2k tokens in our runs.

Goose #

Tested with Goose 1.54.0. Goose's hooks don't pass their output to the model. But both goose session and goose run take extra system instructions and extra MCP servers on the command line, so a small wrapper brings in the brief and the brain's tools together.

ChannelIn GooseHow
Session startWorks, with a wrapper--system carries the brief
Every messageNot availableRecall is the model's call, through MCP
Session endNot availableNothing runs at the end
MCP read and writeWorks--with-extension, started in the project
# ~/.bashrc or ~/.zshrc
goose() {
  case "$1" in
    session|run)
      sub=$1; shift
      command goose "$sub" --system "$(ns-brain wakeup 2>/dev/null)" \
        --with-extension "brain:ns-brain mcp" "$@" ;;
    *) command goose "$@" ;;
  esac
}

Create the brain with plain ns-brain init; there's nothing for --agent to write. The extension starts in the directory you run Goose from, so it finds that project's brain without a path and outside a project the brief is empty. If you'd rather keep the server in Goose's own config, goose configure adds it as a command-line extension running ns-brain mcp.

Codex CLI #

Tested with Codex CLI 0.163.0 and ns-brain 26101611, on a model served through the Responses API. Codex runs Claude-style hooks from its own .codex/hooks.json and carries every channel. Two of its rules decide whether the hooks run at all.

ChannelIn CodexHow
Session startWorksSessionStart in .codex/hooks.json
Every messageWorksUserPromptSubmit, recall on what you typed
Session endWorksStop
MCP read and writeWorks[mcp_servers.brain] in .codex/config.toml
$ cd ~/code/yourproject
$ ns-brain init --agent codex
  + Codex SessionStart hook -> /home/you/code/yourproject/brain/autoexec.sh wakeup --emit codex

Codex runs a hook only after you've reviewed it. The interactive CLI asks the first time it meets one; a headless codex exec skips unreviewed hooks without a word unless you pass --dangerously-bypass-hook-trust, which is for a script that already vets its hooks. Codex also treats a hook's output as JSON whenever it starts with [ and marks the hook failed if that doesn't parse. The brain's recall block starts [brain], so --emit codex hands it over in the JSON Codex reads. If you wired Codex with an older ns-brain, run init again: it replaces the brain's old entries rather than adding a second set.

# .codex/config.toml, for MCP
[mcp_servers.brain]
command = "ns-brain"
args = ["mcp"]
[mcp_servers.brain.env]
BRAIN_HOME = "/home/you/code/yourproject/brain"

Codex reads a project's .codex/ only in a folder it trusts.

Devin CLI #

Tested with Devin CLI 3000.11.92 and ns-brain 26101611. Devin runs .claude/settings.json as Copilot does, so it takes the same wiring as Claude Code. It reads only the JSON form of Claude's reply, which the brain sends it from 26101611.

ChannelIn Devin CLIHow
Session startWorksSessionStart in .claude/settings.json
Every messageWorksUserPromptSubmit, recall on what you typed
Session endWorksStop and SessionEnd
MCP read and writeWorks.devin/mcp_config.json
$ cd ~/code/yourproject
$ ns-brain init --agent devin

That writes Claude Code's hook file, for the reason given under Copilot: one file, so an agent that reads both never gets the brief twice. Devin also reads .devin/hooks.v1.json, so keep the brain out of there. Devin skips a SessionStart entry whose matcher lists Claude Code's session sources. So the brain's entry has no matcher and an older install loses its matcher the next time you run init.

# .devin/mcp_config.json
{ "mcpServers": { "brain": { "command": "ns-brain", "args": ["mcp"] } } }

Devin Cloud is a different product. Devin's documentation says it runs no hooks, so there the brain would be MCP only. We haven't tested it.