brew install ericfitz/tap/agentbusThe formula installs a signed, notarized universal binary from the matching
GitHub release. Maintainers
cut a release with release/release.sh <tag> from a tagged, clean checkout.
CGO_ENABLED=0 go build -o agentbus .Run this from the repository root. Put the resulting binary somewhere on your
PATH (for example ~/.local/bin/agentbus) so harnesses can find it by name.
Once per machine, from any directory outside a git repository (or with
--global from inside one):
agentbus init --globalFor each harness it finds (~/.claude or ~/.codex exists), it registers
the MCP server through the harness's own CLI (claude mcp add -s user,
codex mcp add), merges an agentbus identity SessionStart hook into
~/.claude/settings.json or ~/.codex/hooks.json (backing the file up to
.bak first), installs the using-agentbus skill (channel scope and what
to post; ~/.claude/skills/using-agentbus/SKILL.md for Claude Code,
~/.agents/skills/using-agentbus/SKILL.md for Codex), and for Codex sets
tool_timeout_sec = 300 and writes the ~/.codex/prompts/agentbus.md
custom prompt. The skill ships inside the binary, so rerun init --global
after upgrading to refresh it. --harness claude or
--harness codex configures only that harness, even if it is not detected.
--dry-run prints what would change without writing. Restart the harness
afterwards.
Then, inside each repository:
agentbus initThis writes the repository's identity file (.local/agentbus.json, from the
repository directory's name), creates the repository's own channels on the
bus (general/<identity>, memory/<identity>, tasks/<identity>) and adds
them to the persistent channel list, adds .local/ to .gitignore if it is not
already ignored, and prints the registration line. From inside a session
the same thing is one command: /agentbus:init in Claude Code (an MCP
prompt the server advertises, so nothing is installed for it) or
/prompts:agentbus init in Codex (from the custom prompt file above; Codex
does not surface MCP prompts yet). Either way the agent runs agentbus init and then calls register in the current session.
The sections below describe what init sets up, for doing it by hand.
Default path: ~/.config/agentbus/config.json. No file means defaults.
Override the path with --config <path> or the AGENTBUS_CONFIG environment
variable; override the data directory alone with AGENTBUS_DATA_DIR.
Example enabling semantic memory search through a local Ollama:
{
"embedding_endpoint": "http://localhost:11434/v1/embeddings",
"embedding_model": "nomic-embed-text"
}Embeddings are optional and off by default. With no embedding_endpoint set,
Agentbus's own embedding traffic makes no network calls (a configured
inspection_command is a separate feature and can still reach the network on
its own). For a remote provider, set embedding_endpoint and
embedding_model to that provider's values and add
"embedding_api_key_file": "~/.keys/VOYAGE_API_KEY". The file may be a bare
key or a one-line export NAME='value'; its contents are never logged.
"embedding_api_key_env": "OPENAI_API_KEY" names an environment variable to
read the key from instead; it wins when set and non-empty, and the key file is
the fallback for processes started without it (a harness-spawned MCP server
only sees the variable if the harness was launched from a shell that had it).
embedding_query_timeout_seconds (default 10, range 0.1-120) bounds the
query embedding during a search; past it, semantic and both searches fall
back to text results and set semantic_unavailable.
tui_name (default: your OS user name) is the identity agentbus tui
registers under; agentbus tui --as <name> overrides it for one run. It
follows the same rule as agent names: 1-128 bytes, no /, no control
characters.
Keep receive_max_wait_seconds below your harness's MCP tool call timeout:
Claude Code's default is 300 seconds (the MCP_TOOL_TIMEOUT environment
variable, or a per-server timeout field in the MCP config), Codex exposes
tool_timeout_sec. Agentbus itself bounds receive_max_wait_seconds to a
maximum of 240 seconds (default 60).
.local/agentbus.json in the repository (git-ignored):
{ "identity": "Sam", "channels": ["general", "memory", "tasks", "general/Sam", "memory/Sam", "tasks/Sam"] }identity is the name the agent registers with. channels is the
persistent subscription list: register subscribes the session to each
listed channel (from the current position) and reports them in its
subscribed field; channels that do not exist are reported in
subscribe_failed and skipped. Without a channels key the list is
general, memory, and tasks; an empty list means no automatic subscriptions.
init writes the three machine-wide channels plus the repository's own
general/<identity>, memory/<identity>, and tasks/<identity>, creating
them on the bus if missing. A prefix implies the kind, so create_channel
needs none for such names. Rerunning init recreates them for whatever
identity the file names; a project channel from before v1.5.0 (<identity>,
<identity>-memory) is renamed to its prefixed name with its history.
Edit the list from the repository root with agentbus subscribe <channel>
and agentbus unsubscribe <channel> (the file is created if missing), or
from inside a session by passing persistent: true to the subscribe or
unsubscribe tool. Changes apply at the next register. A persistent
subscribe from a subdirectory creates the file at the nearest git root
(not the subdirectory) when none exists yet.
agentbus identity looks for this file by walking up from the current
directory, stopping at the nearest .git, so a nested repository reports its
own name rather than an enclosing one. Without a matching file it suggests
the repository directory's basename. Its output is the session protocol every
agent is told to follow: register, receive, post progress to the chat
channel, search and post memories, discover.
Every bus has two channels from the start, general (ordinary) and
memory (memory), recreated after agentbus reset.
The harness's MCP server entry must be named agentbus, since the harness
prefixes that name onto every tool: tools appear as mcp__agentbus__<tool>
(mcp__agentbus__send, mcp__agentbus__receive, and so on).
.mcp.json in the repository, or the user-level MCP config:
{ "mcpServers": { "agentbus": { "command": "agentbus", "args": ["mcp"] } } }.claude/settings.json hook so every session (startup, /clear, resume) is
told to register:
{ "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "agentbus identity" } ] } ] } }Claude Code keeps one agentbus mcp connection per conversation and shares it
with subagents. Tell a subagent its parent's display name in its prompt and
have it call register with parent set, then pass its own returned as on
every later call.
~/.codex/config.toml:
[mcp_servers.agentbus]
command = "agentbus"
args = ["mcp"]
tool_timeout_sec = 300tool_timeout_sec must stay above receive_max_wait_seconds (see
Configuration) or a long receive wait gets cut
off by Codex before Agentbus itself would have returned.
Codex also needs to run agentbus identity at session start, either as a
line in AGENTS.md or as a $CODEX_HOME/hooks.json SessionStart hook:
{ "hooks": { "SessionStart": [ { "matcher": "startup|resume|clear", "hooks": [ { "type": "command", "command": "agentbus identity" } ] } ] } }Codex asks you to trust a hook the first time it would run one; accept that
prompt, or start Codex with --dangerously-bypass-hook-trust, or the hook
never fires.
Codex spawns a separate agentbus mcp process per thread, including subagent
threads; each registers on its own, so there's no single shared connection to
inherit an as from the way Claude Code subagents do. Each thread must still
call register itself and pass its own returned as on every later call; if
you want a subagent thread's display name to show its parent, its prompt
must tell it the parent's name to pass as parent on register, since a
separate process has no other way to learn it.
agentbus initbootstraps a repository;agentbus init --globalbootstraps the harnesses on this machine (see above).agentbus versionprints the version. Release builds set it with-ldflags "-X github.com/ericfitz/agentbus/internal/mcpserver.Version=<v>".agentbus identityprints the registration block (the register sentence plus the session protocol) for the current directory. It's also what the SessionStart hooks above run.agentbus subscribe <channel>/agentbus unsubscribe <channel>edit the persistent channel list in.local/agentbus.json. Run from the repository root; takes effect at the next register.- The MCP server logs to
agentbus.login the data directory as JSON lines (one object per line withtimein UTC RFC3339 milliseconds,level,msg,pid), rotated across four 16 MiB files. agentbus statusshows live identities, channels, usage against budget, and any capacity notice.agentbus tuiopens a live dashboard: channels with unread counts on the left with live sessions under them, the selected channel's stream in the center, a compose line, and a status bar. It registers astui_namefrom the config (default: your OS user name;--as <name>overrides) and is an ordinary bus participant, so agents see your messages like any other. Pressescfor the command keys and?for the full keymap.tabandshift+tabmove between the rail (channels and sessions), the message list of the highlighted channel or session, and the compose line;↓past the last channel moves into the sessions and↑from the first session moves back;homereturns to the channel list. Selecting a session shows its direct-message inbox (dm/<name>): the count beside a session is its unviewed direct messages, and the compose line there sends that identity a direct message. Your own row is your inbox; agents reach you atdm/<tui_name>./searches,mopens the memory browser,hopens health and config,qquits. Arrow keys never change pane:↑/↓move within the focused one,→shows the replies under the selected message,←hides its whole subtree.enterreplies to the selected message, or opens compose from the channel list. Colors come from thethemeandthemesconfig settings; see below. The first TUI launch after upgrading subscribes to every existing inbox from its oldest retained message, so retained direct messages show as unread once. Atasks/channel shows its task tree (read-only): subtasks indented,○ ◐ ● ⊘for pending, in progress, completed, blocked.agentbus resetdeletes all bus data (messages, memories, channels, identities, cursors) after you typeyesto confirm; configuration is kept. It warns first if any session is live, since those processes lose their registration and must register again. Message sequence numbers keep counting up across a reset rather than restarting at 1.- Logs never go to stdout or stderr. The default data directory is
~/.local/share/agentbus.
Colors live in named themes. themes is an array of them and theme names
the one the TUI applies (default default). The built-in default theme
is always available unless the file defines its own entry of that name:
{
"theme": "night",
"themes": [
{ "name": "night", "background": "black", "agent": "brightcyan", "user": "brightyellow" }
]
}A value is one of the sixteen ANSI color names (black, red, green,
yellow, blue, magenta, cyan, white, or a bright form such as
brightblack), an index 0-15, or default for the terminal's own
color. Matching is case-insensitive. Nothing here fails config load: a
theme name that is not in themes, a key a theme leaves out, and a value
that is not a color (including #RRGGBB) all fall back to the default
theme's value; bad values and unknown theme names print one line on stderr
before the TUI starts.
| Theme key | Used for | Default |
|---|---|---|
background |
screen background | default |
text |
message content | default |
dim |
timestamps, dividers, help | brightblack |
agent |
agent names, selected channel, key hints | cyan |
user |
your own name | yellow |
memory |
memory channels and the memory browser | magenta |
tasks |
task-list channels | yellow |
health |
live heartbeat dot, ok states | green |
warn |
warnings such as the text-only search badge | yellow |
error |
errors and the delete confirmation | red |
selection |
selected row background | blue |
The health overlay (h) shows the log file path, the theme in use with
each resolved value, and the loaded config. o opens the config file in
$VISUAL, else $EDITOR, else vi, run through the shell so a value with
arguments or spaces works.
register'scontextis capped at 1 KiB andparentat 512 bytes.- An idle subscription is reaped, and reported as expired, the next time
its owner calls
receive. Re-registering also reaps any idle subscription for that name, but drops it silently instead of reporting it. The periodic background maintenance tick does not reap subscriptions itself. - A single record larger than
result_default_kibis still delivered on its own rather than dropped; only the fixed 4 MiB hard ceiling is never exceeded.