A secure local gateway for AI agents to reliably call approved enterprise APIs, tools, and services.
$ npx -y skills add chaitin/OctoBus --agent claude-code
OctoBus is a locally running single-binary gateway for managing pluggable Node.js service packages and exposing the gRPC capabilities in those packages to clients or agents by capset.
The current implementation provides a Go-built octobus binary that is responsible for:
OctoBus is built around the following core model:
service.json, proto files, and a gRPC implementation. A single distribution package can expose multiple service roots through //service-dir.capset -> service -> instance -> method bindings.By default, the daemon listens on a single port, 127.0.0.1:9000. The admin API, gRPC, Connect RPC, MCP, and reflection are all dispatched through that port. You can bind explicitly to another address with --addr, for example 0.0.0.0:9000; when exposing OctoBus remotely, you are responsible for network access control. The CLI performs management operations through the admin API by default and does not write SQLite directly.
Service packages use the long-running runtime mode by default: after an instance is created or started, OctoBus launches a resident Node.js gRPC subprocess. A package can also declare "runtime":{"mode":"on-demand"} in service.json: such instances are not prestarted and do not store a PID or listen address. For each incoming request, OctoBus starts one short-lived invoke subprocess.
OctoBus is published as the @chaitin-ai/octobus npm package. The main package
installs a small Node.js launcher and pulls the matching native Go binary through
platform-specific optional dependencies such as
@chaitin-ai/octobus-linux-x64.
npm install -g @chaitin-ai/octobus
octobus serve --dev
You can also run it without a global install:
npx @chaitin-ai/octobus serve --dev
The npm package installs the octobus binary only. Normal service import and
runtime flows still require node, npm, protoc, and git as described
below.
The Docker image includes the octobus binary and the runtime dependencies used
for normal service import and instance startup flows.
docker run --rm \
-p 9000:9000 \
-e OCTOBUS_BOOTSTRAP_ADMIN_TOKEN=... \
-v octobus-data:/var/lib/octobus \
ghcr.io/chaitin/octobus:latest
The container listens on 0.0.0.0:9000 by default and stores daemon state under
/var/lib/octobus.
The image runs as the non-root octobus user. The named volume example above
is already initialized with the correct ownership. If you use a host bind mount,
create the directory first and make it writable by the container's octobus
user before starting the daemon:
mkdir -p ./octobus-data
# Replace <uid>:<gid> with the values used by the image:
docker run --rm --entrypoint id ghcr.io/chaitin/octobus:latest -u octobus
docker run --rm --entrypoint id ghcr.io/chaitin/octobus:latest -g octobus
sudo chown -R <uid>:<gid> ./octobus-data
docker run --rm \
-p 9000:9000 \
-v "$PWD/octobus-data:/var/lib/octobus" \
ghcr.io/chaitin/octobus:latest
After the first checkout, build the binary:
task build
Start with the default configuration:
./bin/octobus serve --dev
Common options:
./bin/octobus serve --dev \
--data-dir .octobus \
--addr 127.0.0.1:9000
You can also override defaults through environment variables:
export OCTOBUS_DATA_DIR="./.octobus"
export OCTOBUS_ADDR="127.0.0.1:9000"
export OCTOBUS_ADMIN_TOKEN="octobus-dev-admin-token"
The data directory stores the SQLite database, service artifacts and runtimes, instance config, and logs. The default data directory is .octobus under the current directory where the daemon command is started.
By default, service runtimes inherit the daemon's environment and can access anything the daemon user can. Pass --runtime-hardening=node (or set OCTOBUS_RUNTIME_HARDENING=node) to restrict every service runtime:
./bin/octobus serve --runtime-hardening=node
Levels are named after what enforces the restrictions, because the concrete mechanism differs per platform:
| Level | What enforces it |
|---|---|
off (default) | nothing; runtimes launch just as the daemon runs |
node | the Node.js process itself |
At level node, each runtime:
PATH, LANG, LC_ALL, TZ, proxy and CA settings, and on Windows SystemRoot, PATHEXT, and COMSPEC) plus the OCTOBUS_* context. Other daemon variables and any existing NODE_OPTIONS are dropped.HOME, USERPROFILE, and the temp directory variables (TMPDIR, TEMP, TMP) pointing into its instance directory. Temporary files left there are not cleaned up automatically.octobus.db and other instances), start child processes, use worker threads, or load native addonsdns.lookup, fetch, net.connect, and net.Socket.prototype.connect, so a name is judged on the address it resolves to as the connection is made and each redirect is judged again--max-old-space-size)Level node requires Node.js 22.13+, 23.5+, or 24+. At startup the daemon writes the egress rules into its data directory, checks the version of the node on PATH, then launches node once with the same preparation a runtime gets (environment, NODE_OPTIONS including the rules, temp directory, and process group), using a temporary directory in place of the service and instance directories. The daemon refuses to start if either check fails. A version-manager shim that needs variables outside the allowlist (for example Volta or asdf) fails this check; put a real node binary first on PATH. The check runs only at startup, so restart the daemon after changing the node on PATH. On Node.js 25+, whose permission model blocks network access by default, the daemon grants it with --allow-net. Services that start child processes, use worker threads, or load native addons do not work at this level.
Because the Node.js process checks these restrictions itself rather than the operating system enforcing them, treat this level as protection against mistakes in trusted service code, not as isolation for untrusted code:
NODE_OPTIONS, which only Node.js reads. A runtime entry that is not a Node.js script, and any program other than Node.js that it runs, gets only the environment allowlist and the process group. An entry that passes its own permission flags can widen its grants.SIGINT, SIGTERM, or SIGHUP (unless SIGHUP was ignored at startup, as under nohup). A daemon that exits any other way (SIGKILL, a crash, or Ctrl-\) leaves them running, and they must be stopped by hand.service import still runs npm, including package lifecycle scripts, with the daemon's environment.To run the daemon locally and perform normal service import/start workflows, install the following commands and ensure they are available in PATH:
node: runs imported Node.js service packages; the version must satisfy the package's own requirementsnpm: fetches npm packages during service import and installs production dependencies in runtime dirsprotoc: compiles proto descriptors during service importgit: fetches and archives packages imported from HTTPS Git sourcesIf go build or task build fails with timeouts when downloading Go modules (e.g. dial tcp ... i/o timeout from proxy.golang.org), you may need to configure a Go module proxy:
go env -w GOPROXY=https://goproxy.cn,direct
The following example uses the built-in calculator service to run through a complete workflow. Before starting, build the binary, start the daemon as described above, and verify that the CLI can connect:
./bin/octobus status
If the daemon is not running at the default address, specify it through a global option or an environment variable. A local daemon uses HTTP/h2c by default, and the address can be a bare host:port or http://host:port:
./bin/octobus --addr 127.0.0.1:19001 status
OCTOBUS_ADDR=http://127.0.0.1:19001 ./bin/octobus service list
Use the https://host:port form only when OctoBus is remotely exposed and TLS is provided by an outer proxy.
The calculator example installs dependencies through build artifacts from this repository's local SDK. Before running the example from a clean checkout, prepare the example dependencies; this task automatically builds the local SDK and installs example dependencies:
task example:calculator:dev-depsThe repository also provides an on-demand runtime calculator example at
examples/calculator-on-demand-js. To run that example locally, prepare its dependencies first; this task also automatically builds the local SDK:task example:calculator-on-demand:dev-deps
You can also run the clean-checkout smoke script for the local calculator happy path. This task cleans generated artifacts, rebuilds the binary and local SDK, installs calculator example dependencies, starts a temporary daemon, imports the service, creates an instance and capset, and calls Connect RPC to assert that the response is result: 42:
task example:clean-checkout-smoke
Import the example service package:
./bin/octobus service import calculator ./examples/calculator-js
octobus is a Claude Code plugin with 1 hand-picked skill, indexed on Flowy in the Operations category. Install it with the command on its page. It includes octobus-service-package. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it