Nanoboss
Inversion of control for agentic coding β deterministic TypeScript procedures that orchestrate AI agents via ACP
Ask AI about Nanoboss
Powered by Claude Β· Grounded in docs
I know everything about Nanoboss. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
nanoboss
Nanoboss is a procedure-oriented runtime for agent workflows. Instead of centering one generic inner agent loop, it lets workflow code decide continuity, checkpoints, validation, recovery, and what counts as "done".
Architecture
At a high level, nanoboss has adapter entrypoints for the TUI, HTTP/SSE, ACP
stdio, and MCP stdio. Those adapters converge on @nanoboss/app-runtime, which
coordinates procedure discovery, procedure execution, durable run/ref storage,
and downstream ACP agent sessions through the package layer below it.
For the full package map, runtime flow, and refactor review, see docs/architecture.md.
Prerequisites
You need all of the following before nanoboss will be useful:
- Bun installed locally
- this repo checked out if you are running from source
- at least one supported downstream agent stack installed
- that agent authenticated with its own CLI before you start nanoboss
Nanoboss does not perform provider login for you. nanoboss doctor checks
installation and transport readiness, and nanoboss doctor --register
configures MCP. Neither command authenticates Claude, Codex, Gemini, or
Copilot on your behalf.
Supported downstream agents
| Provider | What nanoboss launches | Extra requirement | Auth note |
|---|---|---|---|
| Copilot | copilot --acp --allow-all-tools | none beyond the Copilot CLI | complete the Copilot CLI sign-in flow first |
| Gemini | gemini --acp | none beyond the Gemini CLI | complete the Gemini CLI auth flow first |
| Claude | claude-code-acp | install the Claude CLI and the Zed ACP broker package @zed-industries/claude-code-acp | authenticate the Claude CLI first |
| Codex | codex-acp | install the Codex CLI and the Zed ACP broker package @zed-industries/codex-acp | authenticate the Codex CLI first |
If you want Claude or Codex as downstream agents, install the ACP broker
commands globally so claude-code-acp / codex-acp are on your PATH:
npm install -g @zed-industries/claude-code-acp @zed-industries/codex-acp
Install dependencies
From the repo root:
bun install
Build and install the nanoboss binary
The build command is:
bun run build
This compiles the standalone nanoboss binary and installs it onto your
PATH. The build output also reports the final emitted binary size plus an
estimated breakdown of the embedded bundle versus the Bun runtime, including
top bundled app areas and dependencies.
By default the build installs nanoboss into the first suitable user-owned
PATH location, preferring ~/.local/bin, then ~/bin, then ~/.bun/bin.
It also leaves the compiled artifact at:
dist/nanoboss
To override the install location:
NANOBOSS_INSTALL_DIR=~/bin bun run build
Each build embeds the current git commit hash. On startup, the server and CLI
print a banner like nanoboss-<commit>.
During source-tree development you can also run nanoboss without building:
bun run nanoboss --help
First-time setup
The shortest reliable setup flow is:
-
Install repo dependencies with
bun install. -
Build nanoboss with
bun run buildif you want an installednanobossbinary. For source-tree development,bun run nanoboss ...is fine. -
Install and authenticate at least one downstream agent CLI.
-
Run the doctor command to inspect what nanoboss can currently see:
nanoboss doctoror from source:
bun run nanoboss doctor -
Register the global
nanobossMCP server for supported agents:nanoboss doctor --registeror from source:
bun run nanoboss doctor --register -
Start the CLI:
nanoboss clior from source:
bun run nanoboss cli -
Use
/modelinside the CLI to inspect or change the default provider/model. The available model list comes from the installed ACP harness for each provider, so it can vary by account access, harness version, and provider capabilities.
What doctor and doctor --register actually do
nanoboss doctor prints a table showing:
- which agent CLIs are installed
- whether their ACP path is usable
- whether the standard MCP setup path is available
Typical output looks like:
Agents ACP Global MCP
Claude Code zed ACP broker ... [setup] nanoboss doctor --register
Codex zed ACP broker ... [setup] nanoboss doctor --register
Gemini CLI native ... [setup] nanoboss doctor --register
Copilot CLI native ... [setup] nanoboss doctor --register
nanoboss doctor --register configures one globally registered stdio MCP
server named nanoboss and repairs stale or broken registrations. It covers:
- Claude Code via
claude mcp add - Codex via
codex mcp add - Gemini CLI by writing
~/.gemini/settings.json - Copilot CLI by writing
~/.copilot/mcp-config.json
This is the standard way to make durable nanoboss session tools available to downstream agents. Run it again whenever you:
- switch from source-tree execution to an installed binary
- rebuild/reinstall nanoboss and want agents to point at the latest command
- suspect your MCP registration is stale or broken
The registered command depends on how you invoke registration:
- if you run
nanoboss doctor --register, agents will call the installednanobossbinary - if you run
bun run nanoboss doctor --register, agents will call the source checkout through Bun
Choosing and configuring the downstream agent
If you do nothing, nanoboss defaults to Copilot:
copilot --acp --allow-all-tools
You can choose the default agent in three ways:
- Interactively in the TTY CLI with
/model - Explicitly in a session with
/model <provider> <model> - Through environment variables
Examples:
/model
/model gemini
/model gemini gemini-2.5-pro
/model copilot gpt-5.4/xhigh
For /model, nanoboss uses the installed ACP harness as the source of truth for
available models instead of a fully hard-coded in-repo catalog. The models shown
for a provider depend on what that harness currently advertises for your
environment, so results can vary by provider, account access, harness version,
and model capability support.
/model <provider> reuses a recent cached discovery result when one is still
fresh and otherwise refreshes the provider's advertised catalog before listing
models. /model <provider> <model> validates against that discovered catalog,
and if discovery fails nanoboss reports the refresh error instead of silently
falling back to a static full model list.
Environment-variable overrides:
NANOBOSS_AGENT_CMD=gemini \
NANOBOSS_AGENT_ARGS='["--acp"]' \
bun run nanoboss cli
NANOBOSS_AGENT_CMD=claude-code-acp \
NANOBOSS_AGENT_ARGS='[]' \
bun run nanoboss cli
NANOBOSS_AGENT_CMD=codex-acp \
NANOBOSS_AGENT_ARGS='[]' \
bun run nanoboss cli
To set a default model for the startup banner and downstream agent config, use:
NANOBOSS_AGENT_MODEL=gpt-5.4/xhigh
With that set, the CLI banner looks like:
nanoboss-<commit> copilot/gpt-5.4/x-high
If no explicit environment override is present, nanoboss can also reuse the
persisted default selection saved by the TTY /model picker in:
~/.nanoboss/settings.json
Procedure loading
At startup, nanoboss loads built-in procedure packages from ./packages in the
source tree and also loads additional disk procedures from these locations by
default:
./.nanoboss/proceduresin the current repository root~/.nanoboss/procedures
Those procedure roots contain entrypoint files such as
.nanoboss/procedures/review.ts or .nanoboss/procedures/kb/answer.ts.
Nanoboss recursively discovers .ts files that export a default procedure, so
helper modules can live alongside procedure entrypoints without being
registered as slash commands.
See docs/procedure-packages.md for the built-in
and disk procedure layout, discovery rules, and manifest expectations.
The profile directory is the default place for user-defined procedures. When
/create runs inside a git repository, it writes under that repo's
.nanoboss/procedures/. Otherwise it writes under ~/.nanoboss/procedures/.
Unscoped procedures persist as procedures/<name>.ts; scoped procedures persist
as procedures/<package>/<leaf>.ts.
Built-in procedures are exposed as slash commands and include the knowledge-base workflow:
/kb/ingest/kb/compile-source/kb/compile-concepts/kb/link/kb/render/kb/health/kb/refresh/kb/answer
If you need to disable runtime disk command loading, set:
NANOBOSS_LOAD_DISK_COMMANDS=0
TUI extensions
TUI extensions let you add keybindings, chrome (header/footer/status) slots, activity-bar segments, and panel renderers to the nanoboss TUI without patching a core package. They load at TUI startup from three tiers, matching the procedure-loading layout described above.
File layout
Extensions are discovered recursively under the two disk tiers:
<repo>/.nanoboss/extensions/<name>.tsβ repo-scoped, travels with the project (commit it alongside the procedures it collaborates with)<repo>/.nanoboss/extensions/<name>/index.tsβ same scope, directory form~/.nanoboss/extensions/<name>.tsβ profile-scoped, available in every workspace you open~/.nanoboss/extensions/<name>/index.tsβ same scope, directory form
Plus a third, non-disk tier: built-in extensions compiled into
nanoboss itself (e.g. nanoboss-core-ui, which owns nb/card@1).
Extension contract
Each .ts entry default-exports a TuiExtension from
@nanoboss/tui-extension-sdk. A grep-able top-level metadata export keeps
discovery cheap β the catalog reads it statically without executing the
module:
import type { TuiExtension, TuiExtensionMetadata } from "@nanoboss/tui-extension-sdk";
export const metadata: TuiExtensionMetadata = {
name: "acme-hello",
version: "1.0.0",
description: "Greet the world from the footer slot",
};
const extension: TuiExtension = {
metadata,
activate(ctx) {
ctx.registerKeyBinding({
id: "greet", // becomes "acme-hello/greet" after namespacing
category: "custom",
label: "acme hello greet",
match: (data) => data === "\u0001acme-hello-greet\u0001",
run: () => ({ consume: true }),
});
ctx.registerChromeContribution({
id: "badge", // becomes "acme-hello/badge"
slot: "footer",
render: () => ({ acme: true } as unknown as never),
});
},
};
export default extension;
The activate(ctx) function receives a TuiExtensionContext β the only
surface that mutates runtime state. Four register* methods are available:
registerKeyBinding(binding)β add a keyboard/sequence bindingregisterChromeContribution(contribution)β render content into a named chrome slot (header,footer,status, β¦)registerActivityBarSegment(segment)β append a segment to the activity barregisterPanelRenderer(renderer)β handleui.panel({ rendererId, β¦ })calls emitted by procedures (e.g."acme/files-dashboard@1")
There is also a ctx.log.{info,warning,error} logger that routes through
the same status-line pathway core uses, so messages surface to the user
without crashing the TUI.
Id namespacing and precedence
All contribution ids except panel-renderer rendererId are automatically
namespaced <extensionName>/<id> when registered, so two extensions cannot
collide on ids. Panel-renderer rendererIds are the public contract
procedures target via ui.panel(...) and are not namespaced β the
author of the id (e.g. nb/card@1) owns the convention.
Precedence across tiers is repo > profile > builtin. If two extensions
declare the same metadata.name, only the highest-tier copy loads. For
panel renderers registering the same rendererId, the highest-tier copy
wins and lower tiers emit a shadow warning via ctx.log.warning.
Introspection
Inside the TUI, the /extensions slash command prints one line per loaded
extension showing its scope, activation status, contribution counts, and β
for failed extensions β the captured error message. If any extension fails
activate(), an aggregate line ([extensions] N extension(s) failed to activate) is emitted via the status line after boot; the TUI itself keeps
running with the remaining extensions active.
Unified entrypoint
Everything goes through a single nanoboss entrypoint.
Architecture
See docs/architecture.md for a transport-level overview of:
- local CLI over ACP/stdin-stdout
- HTTP/SSE frontend mode
- downstream agent ACP sessions
- global nanoboss MCP over stdio
Commands
Launch the HTTP/SSE server:
bun run nanoboss http
Launch the CLI frontend. By default it starts an owned private loopback HTTP/SSE
server on an ephemeral port for that terminal session. Use nanoboss http when
you want an explicit shared server instead.
When you run the installed nanoboss binary from the nanoboss repo working
copy, the CLI also warns if the executable looks older than the current
build-relevant files in that working tree, so it is easier to notice when you
forgot to rebuild after local changes:
bun run nanoboss cli
Override the server URL if needed:
bun run nanoboss cli --server-url http://localhost:6503
Passing --server-url is connect-only: nanoboss validates the target server and
will never kill or restart it for you. Omit --server-url to get the default
private local server lifecycle.
Inspect agent health and register the global nanoboss MCP server if needed:
bun run nanoboss doctor
bun run nanoboss doctor --register
The internal stdio ACP server is still available for local CLI mode:
bun run nanoboss acp-server
Nanoboss standardizes on one globally registered nanoboss MCP stdio server across Claude, Gemini, Codex, and Copilot.
Tool call progress lines are shown by default. Hide them with:
bun run nanoboss cli --no-tool-calls
By default the local REPL path spawns copilot --acp --allow-all-tools. In that default path,
nanoboss does not set a model, so the downstream Copilot CLI uses its own
default model unless a procedure selects one explicitly. Override the downstream
agent with NANOBOSS_AGENT_CMD and NANOBOSS_AGENT_ARGS if needed.
To set a default model for the startup banner and downstream agent config, use:
NANOBOSS_AGENT_MODEL=gpt-5.4/xhigh
With that set, the CLI banner looks like:
nanoboss-<commit> copilot/gpt-5.4/x-high
To lint the repository:
bun run lint
Testing
Run the fast local test suite:
bun run test
This runs a compact wrapper around the unit test suite and emits . for pass, S for skip, and F for fail,
then prints detailed failure output only when tests fail. It targets the root tests/unit suite only; it does
not run package-local tests under packages/*/tests.
Run unit tests only:
bun run test:unit
This runs the compact test wrapper against tests/unit.
Run every test file with raw bun test discovery:
bun run test:raw
This includes root unit tests, root e2e tests, and package-local tests discovered under packages/*/tests.
Running literal bun test at the repo root has the same discovery behavior.
Run end-to-end tests with the default gating behavior:
bun run test:e2e
This runs the compact test wrapper against tests/e2e. Real-agent tests are present in that directory, but they remain skipped
unless NANOBOSS_RUN_E2E=1 is enabled.
Run the full real-agent end-to-end suite:
bun run test:e2e:real
This runs NANOBOSS_RUN_E2E=1 bun run scripts/compact-test.ts tests/e2e and exercises the real downstream agents.
Run the /default multi-turn history real-agent coverage only:
NANOBOSS_RUN_E2E=1 bun run scripts/compact-test.ts tests/e2e/default-history-agents.test.ts
That file contains 4 independent tests:
- Claude
- Gemini
- Codex
- Copilot
Run a single real-agent /default history test by name:
NANOBOSS_RUN_E2E=1 bun run scripts/compact-test.ts tests/e2e/default-history-agents.test.ts --test-name-pattern claude
Replace claude with gemini, codex, or copilot as needed.
If you need Bun's native reporter output for debugging, run bun run test:raw.
Typed downstream agent outputs should use jsonType(...) from @nanoboss/procedure-sdk with concrete typia
inputs, for example jsonType<Result>(typia.json.schema<Result>(), typia.createValidate<Result>()),
instead of handwritten schema/validator descriptors. Bun preload for the typia transform is configured
in bunfig.toml.
Package Development
Each workspace package can be developed in isolation from its own directory:
cd packages/<name> && bun test
cd packages/<name> && bun run typecheck
cd packages/procedure-sdk && bun run build
cd packages/procedure-sdk && bun run test:hermetic
From the repo root, bun run test:packages and bun run typecheck:packages
fan those commands out across every package. bun run check:precommit now runs
both package fan-out commands alongside the existing root lint, typecheck,
knip, root test checks, and the sealed procedure-sdk package boundary checks
for cd packages/procedure-sdk && bun run build and
cd packages/procedure-sdk && bun run test:hermetic.
Use these commands when you want specific scopes:
bun run testRoot compact unit suite only.bun run test:raworbun testAll discoverable Bun tests in the repo, including package tests.bun run test:packagesEach package's declaredtestscript, run package-by-package.
Cross-package imports are governed by the ALLOWED_LAYERING table in
tests/unit/package-dependency-direction.test.ts. That test enforces two
rules:
- every
@nanoboss/*import used by a package must be declared in that package'sdependencies - every declared workspace dependency must appear in that package's allowed layering entry
Current allowed layering:
contracts -> (none)
app-support -> (none)
procedure-sdk -> contracts
store -> app-support, contracts, procedure-sdk
agent-acp -> contracts, procedure-sdk, store
procedure-catalog -> app-support, procedure-sdk
procedure-engine -> agent-acp, contracts, procedure-catalog, procedure-sdk, store
app-runtime -> agent-acp, app-support, contracts, procedure-catalog, procedure-engine, procedure-sdk, store
adapters-mcp -> app-runtime, app-support, contracts, procedure-sdk, store
adapters-http -> agent-acp, app-runtime, app-support, procedure-sdk
adapters-tui -> adapters-http, agent-acp, app-support, contracts, procedure-engine, procedure-sdk, store
adapters-acp-server -> agent-acp, adapters-mcp, app-runtime, app-support, contracts, procedure-engine
To add a new @nanoboss/* dependency edge, update both the consuming
package's dependencies in packages/<name>/package.json and that package's
entry in ALLOWED_LAYERING, then run
bun run test:unit tests/unit/package-dependency-direction.test.ts or
bun run check:precommit
to prove the new edge is intentional and still acyclic.
