Local agent runtime over SSH

Rabbit

Give coding agents controlled access to real servers. Typed tools, explicit policy, native OpenSSH, verified transfers, and bounded context.

4
clients
11
tools
MIT
license
git clone https://github.com/dancer/rabbit.git
Claude CodeCodexOpenCodePi
On this page
  1. 01Overview
  2. 02Install
  3. 03Configuration
  4. 04Host Setup
  5. 05Auth
  6. 06Doctor
  7. 07Commands
  8. 08Policies
  9. 09Uploads
  10. 10Breakglass
  11. 11Audit
  12. 12Claude Code
  13. 13Codex
  14. 14OpenCode
  15. 15Pi
  16. 16Context Limits
session prod.app
policy guarded
01rabbit:start

host prod, target app

ready
02rabbit:plan

action put, risk medium

allowed
03rabbit:put

config/app.toml, sha256 verified

committed
agentrabbit policyopensshapproved host

01Overview

Rabbit is a local runtime that gives agents controlled access to approved servers.

Rabbit lets agents inspect and operate real servers through a local, policy-controlled runtime.

The runtime reads your local SSH configuration, checks every request against Rabbit policy, runs bounded read commands, and keeps the trust boundary on your machine.

Model

Rabbit is built around three layers.

claude code -> stdio mcp -> rabbit core
codex       -> stdio mcp -> rabbit core
opencode    -> stdio mcp -> rabbit core
pi          -> native    -> rabbit core
rabbit core -> openssh   -> approved host

Claude Code, Codex, and OpenCode talk to Rabbit through local stdio MCP. Pi loads a native typed extension. Every client reaches the same Rabbit core, which loads host config, checks policy, builds typed commands, and executes approved actions through native OpenSSH.

Current tools

rabbit.start   start an agent working session
rabbit.audit   read recent audit events
rabbit.breakglass run an explicit emergency command
rabbit.doctor  diagnose setup and remote dependencies
rabbit.hosts   list approved hosts
rabbit.get     read a bounded file from a target
rabbit.plan    check whether an action is allowed
rabbit.put     upload a verified project file
rabbit.restart restart a configured service
rabbit.status  run bounded service status
rabbit.logs    fetch bounded service logs
rabbit.search  run scoped fixed-string search

Read, admin, and breakglass tools return the exact command array, policy decision, exit code, duration, bounded output, and truncation state.

rabbit.put returns compact transfer metadata instead of its internal command or file contents.

Trust boundary

Rabbit does not require a cloud relay, remote daemon, or password auth.

Your local machine remains the trust anchor:

  • SSH aliases come from ~/.ssh/config
  • Host trust comes from normal OpenSSH behavior
  • Rabbit config only approves named hosts
  • The MCP server runs locally over stdio
  • Policies decide what tools an agent can use per host
  • Breakglass must be enabled explicitly for each host

Shape

Rabbit is useful when an agent needs server context without being handed an unbounded shell.

Good Rabbit workflows:

  • start with approved host context
  • diagnose setup and connectivity failures
  • inspect service health
  • fetch recent logs
  • search deploy paths
  • upload reviewed project files
  • plan service operations
  • review host capability
  • prepare safe execution steps

Emergency command execution stays behind an explicit user request, a host-level breakglass capability, destructive tool metadata, bounded execution, and local audit records.

02Install

Install Rabbit locally and connect it to Claude Code, Codex, OpenCode, or Pi.

Rabbit runs locally. Claude Code, Codex, and OpenCode connect over stdio MCP. Pi loads the same Rabbit core through a native extension.

Get Rabbit

git clone https://github.com/dancer/rabbit.git
cd rabbit
bun install
bun run verify

Keep this checkout in a stable location. Client config points to packages/mcp/src/main.ts.

Choose a client

Claude Code

cd /absolute/path/to/project
claude mcp add \
  --env RABBIT_ROOT="$PWD" \
  --transport stdio \
  --scope local \
  rabbit -- \
  bun /absolute/path/to/rabbit/packages/mcp/src/main.ts

Codex

cd /absolute/path/to/project
codex mcp add rabbit \
  --env RABBIT_ROOT="$PWD" -- \
  bun /absolute/path/to/rabbit/packages/mcp/src/main.ts

OpenCode

Add Rabbit to opencode.json in the project root:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "rabbit": {
      "type": "local",
      "command": ["bun", "/absolute/path/to/rabbit/packages/mcp/src/main.ts"],
      "cwd": "/absolute/path/to/project",
      "environment": {
        "RABBIT_ROOT": "/absolute/path/to/project"
      },
      "enabled": true
    }
  }
}

Pi

pi install git:github.com/dancer/rabbit

Pi installs Rabbit as a native extension rather than an MCP server.

Verify

Ask the agent to list Rabbit hosts.

Use rabbit.hosts and show me the approved hosts.

For Pi, use rabbit_hosts. Pi renders it as rabbit:hosts.

An empty host list means Rabbit is connected but the project has not approved a host yet.

Requirements

  • Bun
  • OpenSSH client
  • key-based SSH access
  • host aliases in ~/.ssh/config
  • rabbit.toml for approved hosts

Rabbit does not put SSH credentials in agent config. OpenSSH continues to own keys, host verification, and authentication.

03Configuration

Define approved hosts, capabilities, targets, services, paths, and audit location.

Rabbit resolves the project root in this order:

RABBIT_ROOT
CLAUDE_PROJECT_DIR
current working directory

Set RABBIT_ROOT in client config when the Rabbit source checkout and the project using Rabbit are different directories.

Rabbit then searches for rabbit.toml in this order:

./rabbit.toml
./.rabbit/rabbit.toml
~/.config/rabbit/rabbit.toml

Minimal config

[host.prod]
ssh = "prod"
cap = ["doctor", "get", "status", "logs", "search"]
label = ["api"]

host.prod is the Rabbit host name. ssh = "prod" points to the local OpenSSH alias.

Targets

Targets give agents named surfaces instead of arbitrary paths.

[host.prod]
ssh = "prod"
cap = ["doctor", "get", "put", "status", "logs", "search", "restart"]
label = ["api"]

[host.prod.service]
app = "rabbit"

[host.prod.path]
root = "/srv/rabbit"
log = ["/var/log/rabbit/app.log"]

[host.prod.target.app]
service = "rabbit"
path = "/srv/rabbit/current"
log = ["/var/log/rabbit/app.log"]

With this config, rabbit.status against target app runs:

["systemctl", "status", "rabbit", "--lines", "100", "--no-pager"]

Target-scoped calls use only that target's bindings. They do not fall back to host-level paths or logs.

Path-backed operations require absolute configured paths. rabbit.get and rabbit.put require a named target. Host-level rabbit.search requires an absolute root or home path.

Local upload sources are resolved from the project root and remote destinations are resolved inside the named target path.

The configured target path may be a symlink to a release directory. Rabbit treats its physical destination as the boundary and rejects requested parent directories that resolve outside it.

Audit path

audit = "~/.local/state/rabbit/audit.jsonl"

The audit path must stay inside ~/.local/state/rabbit and use the .jsonl extension.

Audit paths support ~.

Capabilities

cap = ["doctor", "status", "logs", "search"]

Capabilities are explicit per host. If omitted, Rabbit defaults to read-only host inspection:

doctor
status
logs
search

Add put only where an agent should be able to create files inside approved target paths:

[host.prod]
ssh = "prod"
cap = ["doctor", "get", "put", "status", "logs", "search"]

[host.prod.target.app]
path = "/srv/rabbit/current"

Arbitrary command execution is disabled unless the host includes breakglass:

[host.prod]
ssh = "prod"
cap = ["doctor", "status", "logs", "search", "breakglass"]

Breakglass applies to the whole approved host and runs with the configured SSH identity. Keep it off hosts that only need named read or service operations.

04Host Setup

Prepare local SSH aliases and server users for Rabbit.

Rabbit discovers hosts from your local SSH config.

SSH alias

Host prod
  HostName 203.0.113.10
  User deploy
  Port 22
  IdentityFile ~/.ssh/prod

Then approve that alias in Rabbit:

[host.prod]
ssh = "prod"
cap = ["doctor", "status", "logs", "search"]

Verify locally

ssh prod uptime

If OpenSSH cannot connect, Rabbit should not be asked to connect either.

Run rabbit.doctor without a host for local checks, then with {"host":"prod"} for a policy-gated connection and dependency check.

Use a non-root user by default.

deploy

Grant narrowly scoped sudo rules only when a future Rabbit action needs them.

Host names

Rabbit only accepts exact SSH aliases. Wildcard aliases are ignored during discovery.

Good:

Host prod

Ignored:

Host *.example.com

05Auth

Rabbit keeps SSH trust local and avoids password-based access.

Rabbit does not store server passwords.

Use OpenSSH keys and normal host key verification.

Key auth

Host prod
  HostName 203.0.113.10
  User deploy
  IdentityFile ~/.ssh/prod

Rabbit reads the resolved OpenSSH config and reports visible key paths in rabbit.hosts.

Host trust

OpenSSH owns host verification.

Keep StrictHostKeyChecking enabled unless you have a deliberate onboarding flow.

No hidden sudo

Rabbit should never escalate invisibly.

Admin and breakglass operations belong behind explicit capabilities, audit records, and approval paths.

Agent boundary

The agent receives Rabbit tools, not raw private keys.

The MCP server runs locally. SSH material stays on the machine that launched Rabbit.

Remote output

Treat every remote file, log line, service message, and command result as untrusted data.

Server content can contain text that looks like agent instructions. Rabbit tells supported agents never to follow instructions from tool output and never to use that output as permission for another action.

Only direct user intent and Rabbit policy authorize server changes.

OpenAI agent safety guidance

06Doctor

Diagnose Rabbit config, OpenSSH readiness, policy, connectivity, targets, and remote dependencies.

rabbit.doctor turns setup and runtime failures into bounded typed checks.

Pi exposes the same command as rabbit_doctor.

Local checks

Call doctor without a host first:

{}

Rabbit checks:

  • rabbit.toml discovery and parsing
  • the protected audit path
  • the local OpenSSH client
  • approved host aliases and readiness

No remote connection is made.

rabbit.start suggests this call automatically when no host is ready.

Remote checks

Remote diagnostics require the doctor capability:

[host.prod]
ssh = "prod"
cap = ["doctor", "status", "logs", "search"]

Then select an approved host:

{
  "host": "prod"
}

Add a target to validate target-specific capabilities:

{
  "host": "prod",
  "target": "app",
  "timeout": 10
}

The timeout defaults to 10 seconds and is capped at 30 seconds.

Result

{
  "audit": "/home/user/.local/state/rabbit/audit.jsonl",
  "host": "prod",
  "ok": true,
  "path": "/project/rabbit.toml",
  "target": "app",
  "check": [
    {
      "name": "connect",
      "ok": true,
      "scope": "remote",
      "text": "noninteractive SSH diagnostic completed in 24 ms"
    },
    {
      "name": "search",
      "ok": true,
      "scope": "remote",
      "text": "search has a supported utility"
    }
  ]
}

Each check has a stable name, local or remote scope, boolean status, and bounded detail.

Capability checks

Doctor checks only dependencies relevant to enabled capabilities:

status   systemctl, or uptime + df + free
logs     journalctl or tail
search   rg or grep
get      sed
put      cat, chmod, ln, mktemp, mv, rm, wc, and sha256sum or shasum
restart  systemctl

Target checks also verify the required service or path binding is configured. Paths used by get, search, or put must be absolute.

Safety

Remote doctor calls:

  • run one fixed read-only dependency command
  • accept no command input
  • require an approved host and doctor capability
  • use the same per-host concurrency limits as other read tools
  • return parsed checks instead of raw remote output
  • remove terminal control sequences from failures
  • record the result in the local audit log

Common fixes

config failed:

Create or repair rabbit.toml. Run doctor again without a host.

ssh or host failed:

Repair the exact alias in ~/.ssh/config, then verify ssh prod uptime.

policy failed:

Add doctor to that host's capability list if remote diagnostics are intended.

connect failed:

Fix host verification, key authentication, jump host, user, or network access through normal OpenSSH.

search, put, or another capability failed:

Install the listed remote utility or remove the unsupported capability from that host.

07Commands

The Rabbit command language exposed to agents through MCP tools.

Rabbit uses product command names in docs and MCP-safe tool names in protocol.

rabbit:start   -> rabbit.start
rabbit:audit   -> rabbit.audit
rabbit:breakglass -> rabbit.breakglass
rabbit:doctor  -> rabbit.doctor
rabbit:get     -> rabbit.get
rabbit:put     -> rabbit.put
rabbit:status  -> rabbit.status
rabbit:logs    -> rabbit.logs
rabbit:search  -> rabbit.search
rabbit:restart -> rabbit.restart
rabbit:plan    -> rabbit.plan
rabbit:hosts   -> rabbit.hosts

rabbit.start

Starts a guided agent session from approved host context.

{
  "host": "prod",
  "target": "app"
}

Returns the selected host, config issues, and the safest next tool sequence.

{
  "host": [{ "name": "prod", "ready": true }],
  "step": [
    {
      "tool": "rabbit.plan",
      "reason": "check policy before touching the host",
      "input": { "action": "status", "host": "prod", "target": "app" }
    },
    {
      "tool": "rabbit.status",
      "reason": "inspect current service and system health",
      "input": { "host": "prod", "target": "app" }
    }
  ]
}

rabbit.doctor

Diagnoses local setup or an approved host without accepting arbitrary commands.

{
  "host": "prod",
  "target": "app"
}

Run it without arguments for local config, audit, OpenSSH, and host readiness checks. Remote checks require the doctor capability and return only bounded parsed diagnostics.

rabbit.audit

Reads a bounded recent slice from Rabbit's local audit log.

{
  "limit": 20
}

The result includes recent action, policy, timing, and output metadata without reading the whole audit file into context.

rabbit.hosts

Lists approved hosts, capabilities, labels, SSH alias, local readiness, services, paths, targets, and issues.

{
  "host": [
    {
      "name": "prod",
      "ssh": "prod",
      "ready": true,
      "cap": ["doctor", "status", "logs", "search"]
    }
  ],
  "issue": []
}

rabbit.get

Reads a bounded file from a named target.

{
  "host": "prod",
  "target": "app",
  "path": "config/app.toml",
  "limit": 100
}

The configured target path must be absolute. The requested path must be relative to that target. Absolute requests and parent traversal are denied.

Rabbit resolves the configured target and requested parent directory to physical paths. Parent symlinks may stay inside the target, but escapes are denied. The final source must be a regular file and must not be a symlink.

rabbit.plan

Checks whether an action is allowed.

{
  "host": "prod",
  "action": "restart",
  "target": "app"
}

Returns:

{
  "allow": true,
  "kind": "admin",
  "risk": "high",
  "note": ["target app maps to service rabbit"]
}

rabbit.put

Streams one project-local regular file into an approved target path.

{
  "host": "prod",
  "target": "app",
  "source": "config/app.toml",
  "path": "config/app.toml",
  "reason": "publish the reviewed app config"
}

Rabbit defaults to mode 0644, refuses to replace existing files, caps files at 16 MiB, verifies SHA-256 remotely, and commits atomically.

See Uploads for the complete transfer and failure model.

rabbit.restart

Restarts a configured target service.

{
  "host": "prod",
  "target": "app",
  "reason": "apply config change"
}

Rabbit only restarts services mapped by the target configuration. The action must be allowed by host policy and is marked destructive in MCP metadata.

rabbit.breakglass

Runs an exact command array on a host with the explicit breakglass capability.

{
  "command": ["sh", "-lc", "id && uptime"],
  "host": "prod",
  "reason": "diagnose a production incident",
  "timeout": 45
}

Rabbit requires a reason, defaults to a 30 second timeout, caps the timeout at 300 seconds, and marks the action as high risk. It does not add a shell or elevate privileges unless the exact command requests them.

rabbit.status

Runs bounded status inspection.

{
  "host": "prod",
  "target": "app"
}

Returns typed execution data:

{
  "allow": true,
  "command": ["systemctl", "status", "rabbit", "--lines", "100", "--no-pager"],
  "code": 0,
  "duration": 24,
  "ok": true,
  "stdout": "rabbit.service active",
  "stdoutbytes": 21,
  "stderr": "",
  "stderrbytes": 0,
  "timedout": false,
  "truncated": false
}

rabbit.logs

Runs bounded log inspection.

{
  "host": "prod",
  "target": "app",
  "limit": 100
}

rabbit.search

Runs fixed-string search inside an approved target path.

{
  "host": "prod",
  "target": "app",
  "query": "database timeout"
}

Search requires an absolute approved target path or host root path. Rabbit does not search the remote working directory by default.

Rabbit resolves the configured search root to its physical path before running bounded fixed-string search.

08Policies

Rabbit policy turns server access into explicit host capabilities.

Rabbit policy starts with host approval.

If a host is not in rabbit.toml, it does not exist to the agent.

Capability classes

Rabbit classifies capabilities by risk.

read        doctor, status, logs, search, get
write       put
admin       start, stop, restart, deploy
breakglass  breakglass

The highest capability on a host determines the host kind.

breakglass is never added by default. It must appear in the host capability list.

Allow by capability

[host.prod]
ssh = "prod"
cap = ["doctor", "status", "logs", "search"]

rabbit.restart will be denied because restart is not in cap.

rabbit.put is also denied unless put appears explicitly. Write actions never enter the default capability set.

Denial shape

Denied plans are still useful.

They return why the action cannot proceed:

{
  "allow": false,
  "note": ["action restart is not allowed on prod"]
}

Target checks

Targets are named service or path bindings.

[host.prod.target.app]
service = "rabbit"
path = "/srv/rabbit/current"

When a target is unknown, Rabbit includes that in the plan notes.

rabbit.get, rabbit.search, and rabbit.put require absolute configured roots. Uploads also require a named target. Both the local source and remote destination must stay within their approved roots.

Configured target roots may be symlinks, which supports release layouts such as current. Rabbit resolves the configured root and each requested parent directory physically, then denies parents that escape the canonical target.

Write checks

Plan writes before execution:

{
  "action": "put",
  "host": "prod",
  "target": "app"
}

overwrite defaults to false. Set it to true only when replacing the destination is the intended operation.

Breakglass checks

Plan breakglass before execution:

{
  "action": "breakglass",
  "host": "prod"
}

Rabbit still requires the host to be present in local SSH config and ready for noninteractive access. A permitted plan does not grant more privileges than the configured remote user already has.

09Uploads

Stream verified project files into approved target paths with atomic commit and no-overwrite defaults.

rabbit.put uploads one project-local regular file into one approved target.

File bytes move from the local project to native OpenSSH stdin. They never become tool arguments, model-visible text, or audit content.

Clients that request MCP progress receive monotonic byte updates every MiB and at completion. Progress delivery failures never interrupt the transfer.

Configure access

The host needs the put capability and the target needs an absolute path.

[host.prod]
ssh = "prod"
cap = ["doctor", "get", "put", "status", "logs", "search"]

[host.prod.target.app]
path = "/srv/rabbit/current"

Rabbit denies uploads to host-level paths. Every destination must belong to a named target.

Plan first

{
  "action": "put",
  "host": "prod",
  "target": "app"
}

Continue only when the plan returns allow: true.

Upload

{
  "host": "prod",
  "target": "app",
  "source": "config/app.toml",
  "path": "config/app.toml",
  "mode": "0644",
  "overwrite": false,
  "reason": "publish the reviewed app config",
  "timeout": 30
}

source is relative to the local project root. path is relative to the approved remote target path.

mode, overwrite, and timeout are optional:

mode       0644
overwrite  false
timeout    30 seconds

Rabbit caps timeout at 300 seconds.

Result

Upload results stay compact.

{
  "action": "put",
  "allow": true,
  "bytes": 12,
  "checksum": "9f3e...",
  "code": 0,
  "destination": "/srv/rabbit/current/config/app.toml",
  "duration": 84,
  "kind": "write",
  "mode": "0644",
  "ok": true,
  "overwrite": false,
  "source": "config/app.toml",
  "stderr": "",
  "timedout": false,
  "truncated": false
}

The internal shell program and remote acknowledgement are not returned to the model.

Verification path

Rabbit performs these steps:

  1. Evaluate host capability and target policy before reading the local file.
  2. Resolve the project root and source path.
  3. Reject absolute paths, parent traversal, directories, project escapes, and files over 16 MiB.
  4. Open the source without following a final symlink and calculate SHA-256 from that descriptor.
  5. Stream the file through native OpenSSH stdin.
  6. Resolve the configured target and destination parent to physical remote paths.
  7. Reject destination parents that resolve outside the canonical target.
  8. Create a private temporary file in the destination directory.
  9. Verify remote byte count and SHA-256 before applying the requested mode.
  10. Commit in the same directory and require a final acknowledgement.
  11. Record transfer metadata in the local audit log.

The remote host must provide sha256sum or shasum.

A target such as /srv/rabbit/current may itself be a symlink to a release directory. Rabbit accepts that configured root, then contains every requested destination inside the resolved release path.

Atomic commit

Rabbit writes the temporary file in the destination directory so commit stays on the same filesystem.

With overwrite: false, Rabbit creates the destination with a hard link. If the destination already exists, the operation fails without replacing it.

With overwrite: true, Rabbit moves the verified temporary file over the destination.

If the filesystem does not permit hard links, the no-overwrite path fails safely.

Boundaries

rabbit.put does not:

  • create missing parent directories
  • upload directories
  • upload more than one file
  • accept absolute local or remote paths
  • read files outside the project through symlinks
  • follow remote parent symlinks outside the canonical target
  • elevate remote privileges
  • expose file contents to the model or audit log

The configured SSH user still needs permission to create and commit files in the target directory.

10Breakglass

Run explicit emergency commands on an approved host with strict bounds and a local audit trail.

Breakglass gives an agent arbitrary noninteractive command execution on one approved SSH host.

It is opt-in, high risk, and never the default path for normal work.

Policy

[host.prod]
ssh = "prod"
cap = ["doctor", "status", "logs", "search", "breakglass"]

Adding breakglass should be a conscious host-level decision.

Workflow

Plan the action first:

{
  "action": "breakglass",
  "host": "prod"
}

Only continue when the plan returns allow: true, the user explicitly requested emergency command access, and the exact command is understood.

{
  "command": ["uname", "-a"],
  "host": "prod",
  "reason": "confirm the production kernel during incident response",
  "timeout": 30
}

Rabbit preserves argument boundaries and shell-quotes every value before passing the command to OpenSSH.

Shell syntax is available only when requested explicitly:

{
  "command": ["sh", "-lc", "id && uptime"],
  "host": "prod",
  "reason": "inspect identity and load during incident response",
  "timeout": 45
}

Bounds

  • command accepts 1 to 64 arguments
  • each argument is limited to 1,000 characters
  • reason is required and limited to 500 characters
  • timeout defaults to 30 seconds and is capped at 300 seconds
  • combined stdout and stderr retention is capped at 12 KB
  • terminal control sequences are removed from returned output
  • cancellation from the agent client stops the SSH process
  • password prompts, stdin, and an interactive TTY are unavailable

The command can do anything allowed by the configured remote SSH identity. Rabbit does not silently elevate privileges. Use an explicit command such as sudo -n only when the server already permits noninteractive sudo.

Audit

Every breakglass call records:

  • host
  • exact command array
  • operator reason
  • policy decision and risk class
  • exit code and duration
  • stdout and stderr byte counts
  • timeout and truncation state

Commands and reasons are stored in the local JSONL audit file. Do not place passwords, tokens, or other secrets in command arguments.

Danger zone

Breakglass is the Rabbit equivalent of the red button.

It exists because real incidents happen, not because agents should operate as root by default.

normal path:     plan -> approved action -> audited execution
breakglass path: explicit user request -> plan -> exact command -> audit

Rabbit marks the MCP tool as destructive, non-idempotent, and open-world. Client approval interfaces remain an additional safeguard, not a replacement for host policy or explicit user intent.

11Audit

Rabbit records operational intent and execution history in JSONL.

Rabbit audit is a local JSONL stream.

Default path:

~/.local/state/rabbit/audit.jsonl

Override it in rabbit.toml:

audit = "~/.local/state/rabbit/audit.jsonl"

Audit files must remain inside ~/.local/state/rabbit, use the .jsonl extension, and resolve without symlinks or hard links.

Rabbit exposes recent bounded audit entries through rabbit.audit.

Each read scans at most the final 256 KiB, skips malformed records in that window, and returns up to the requested entry limit. Corrupted or oversized history cannot force Rabbit to read the entire file.

Record shape

{
  "time": "2026-05-01T10:00:00.000Z",
  "tool": "rabbit.plan",
  "host": "prod",
  "action": "restart",
  "allow": true,
  "kind": "admin",
  "risk": "high",
  "ok": true
}

Why JSONL

JSONL is easy to append, tail, ship, grep, and import.

tail -f ~/.local/state/rabbit/audit.jsonl

Current coverage

Rabbit records:

  • plan requests
  • execution requests
  • policy decision
  • risk class
  • exit code
  • duration
  • stdout and stderr metadata
  • upload source, destination, byte count, SHA-256, mode, and overwrite intent
  • restart reason in request input
  • breakglass command, timeout, and reason in request input

Audit files are created with owner-only permissions. Rabbit also tightens an existing audit file to mode 0600 before appending.

Breakglass commands are intentionally auditable. Keep secrets out of command arguments and reasons.

Upload file contents are never written to the audit log.

12Claude Code

Configure Rabbit as a local MCP server for Claude Code.

Rabbit runs as a local stdio MCP server.

Add server

cd /absolute/path/to/project
claude mcp add \
  --env RABBIT_ROOT="$PWD" \
  --transport stdio \
  --scope local \
  rabbit -- \
  bun /absolute/path/to/rabbit/packages/mcp/src/main.ts

Local scope keeps the server private to you and associates it with the current project.

RABBIT_ROOT tells Rabbit where to find project policy without changing the server process to the Rabbit source directory. Claude Code also provides CLAUDE_PROJECT_DIR, which Rabbit uses as a fallback.

Verify

claude mcp get rabbit
claude mcp list

Both commands should report Rabbit as connected. Inside Claude Code, /mcp shows the server and its tools.

Shared config

Use project scope only when every teammate has Rabbit at a compatible path:

claude mcp add \
  --env RABBIT_ROOT="$PWD" \
  --transport stdio \
  --scope project \
  rabbit -- \
  bun /absolute/path/to/rabbit/packages/mcp/src/main.ts

Claude Code writes .mcp.json and asks each teammate to approve it before first use.

Rabbit config

Keep rabbit.toml in the project that owns the server access policy.

[host.prod]
ssh = "prod"
cap = ["doctor", "status", "logs", "search"]

First prompt

Use rabbit.start for prod app, then follow the suggested safe steps.

Expected tools

Claude Code should see:

rabbit.start
rabbit.audit
rabbit.breakglass
rabbit.doctor
rabbit.get
rabbit.hosts
rabbit.plan
rabbit.put
rabbit.restart
rabbit.status
rabbit.logs
rabbit.search

Rabbit publishes short server instructions that tell Claude Code to start a session, use approved targets, plan write, admin, and breakglass actions, and hard-stop on denied policy.

If setup or connectivity fails, Claude Code should use rabbit.doctor before attempting another remote action.

For uploads, give Claude Code a project-relative source, target-relative destination, and reason. It should plan put first and leave overwrite false unless replacement is intentional.

For breakglass, state the exact command and reason in the prompt. Claude Code should only call it after an explicit emergency request from the user.

Reference

Claude Code MCP documentation

13Codex

Configure Rabbit as a local MCP server for Codex.

Rabbit can run as a local MCP server for Codex.

Add server

cd /absolute/path/to/project
codex mcp add rabbit \
  --env RABBIT_ROOT="$PWD" -- \
  bun /absolute/path/to/rabbit/packages/mcp/src/main.ts

Codex CLI and the Codex IDE extension share MCP configuration.

Project config

For a repository-scoped setup, add this to .codex/config.toml in a trusted project:

[mcp_servers.rabbit]
command = "bun"
args = ["/absolute/path/to/rabbit/packages/mcp/src/main.ts"]
env = { RABBIT_ROOT = "/absolute/path/to/project" }
startup_timeout_sec = 10
tool_timeout_sec = 310

Codex also supports the same table in ~/.codex/config.toml.

The 310 second client timeout lets Rabbit enforce its own maximum 300 second execution timeout without Codex ending the request first.

Verify

codex mcp get rabbit --json
codex mcp list

Inside the Codex terminal UI, /mcp shows active servers and tools.

First prompt

Use Rabbit to inspect prod. Start with rabbit.start for prod app, then follow its suggested steps.

Ask Codex to plan before execution.

Plan a search for the app target on prod, then run rabbit.search if policy allows it.

This keeps the first interaction read-only and easy to inspect.

Upload

Plan a put to prod app. If policy allows it, upload config/app.toml to config/app.toml without overwrite because I reviewed this config for the current release.

Rabbit passes file bytes outside model context and returns only transfer metadata.

Rabbit’s MCP instructions keep the most important policy workflow inside Codex’s first 512 instruction characters.

Use rabbit.doctor when Codex reports a config, host readiness, SSH, target, or remote dependency failure.

Breakglass

Ask for breakglass explicitly and include the exact command plus an audit reason.

Plan breakglass for prod. If policy allows it, run ["uname", "-a"] because I need the production kernel version during this incident.

Rabbit marks the tool as destructive and non-idempotent. It remains disabled unless breakglass is present in the host capability list.

Reference

Codex MCP documentation

14OpenCode

Configure Rabbit as a local MCP server for OpenCode.

OpenCode connects to Rabbit as a local stdio MCP server.

Config

Add opencode.json to the project root:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "rabbit": {
      "type": "local",
      "command": ["bun", "/absolute/path/to/rabbit/packages/mcp/src/main.ts"],
      "cwd": "/absolute/path/to/project",
      "environment": {
        "RABBIT_ROOT": "/absolute/path/to/project"
      },
      "enabled": true
    }
  }
}

cwd keeps the server process aligned with the project. RABBIT_ROOT makes policy discovery explicit.

Verify

opencode mcp list
opencode debug config

Rabbit should report connected with twelve tools.

First prompt

Use the Rabbit tools. Start a session for prod app, then inspect status without changing the server.

OpenCode prefixes MCP tools with the configured server name when it exposes them to the model. Prompt with Rabbit by name instead of depending on a generated tool prefix.

Context

OpenCode warns that every enabled MCP server consumes model context. Rabbit keeps this cost small with twelve focused tools, concise descriptions, compact structured results, and bounded output.

Use rabbit.doctor for setup and connectivity failures before retrying remote tools.

rabbit.put streams local file bytes outside model context. Plan the write first and keep overwrite disabled unless replacement is intentional.

Breakglass is included in the tool list but remains blocked unless the host policy enables it. Ask for it explicitly, plan it first, and provide the exact command and audit reason.

Reference

OpenCode MCP documentation

15Pi

Install Rabbit as a native typed Pi extension.

Pi loads Rabbit as a native extension. It does not need an MCP bridge.

Install

pi install git:github.com/dancer/rabbit

Install only for the current project:

pi install git:github.com/dancer/rabbit -l

Try Rabbit without saving it:

pi -e git:github.com/dancer/rabbit

Verify

pi list

Start Pi in the project that contains rabbit.toml:

cd /absolute/path/to/project
pi

Tool names

Pi tool identifiers use underscores:

rabbit_start
rabbit_audit
rabbit_breakglass
rabbit_doctor
rabbit_get
rabbit_hosts
rabbit_plan
rabbit_put
rabbit_restart
rabbit_status
rabbit_logs
rabbit_search

Pi renders the human-facing labels as rabbit:start, rabbit:audit, and the matching colon form for each tool.

Runtime behavior

The native extension uses the same Rabbit core, schemas, policy engine, audit log, output bounds, and cancellation path as the MCP server.

Read tools can run in parallel. rabbit_put, rabbit_restart, and rabbit_breakglass are sequential.

Run rabbit_doctor without a host for local setup checks or with an approved host for policy-gated remote dependency checks.

Uploads require a permitted plan, project-relative source, target-relative destination, and audit reason. Breakglass requires an explicit user request, a permitted plan, an exact command array, and an audit reason.

First prompt

Call rabbit_start for prod app. Follow only the approved next steps and stop if policy denies the request.

Reference

Pi extension documentation

16Context Limits

Keep Rabbit useful in long agent sessions by making every tool small, typed, and bounded.

Agent context is a product constraint.

Every tool result competes with the user's task, code, logs, plans, and previous tool calls. Rabbit should return enough information to act safely, then stop.

Current limits

Claude Code warns when MCP tool output exceeds 10,000 tokens and defaults to a maximum of 25,000 output tokens. MCP tools can also declare anthropic/maxResultSizeChars in tool metadata.

Rabbit uses smaller outputs by design:

  • typed structured results
  • compact JSON text
  • twelve focused tools
  • concise server instructions
  • bounded log limits
  • 256 KiB audit scan window
  • fixed-string search with line limits
  • command arrays instead of shell paragraphs
  • file streaming outside model context
  • resources for state that agents can fetch deliberately

CI caps the serialized tool catalog at 28,000 characters and every individual tool at 4,000 characters.

Design rule

Rabbit tools should answer one operational question at a time.

Good:

Fetch the current status for prod app.

Bad:

Dump every host, every log, every service, and every recent command.

Tool output shape

Each Rabbit tool should return:

{
  "allow": true,
  "command": ["systemctl", "status", "rabbit", "--lines", "100", "--no-pager"],
  "code": 0,
  "duration": 24,
  "kind": "read",
  "note": [],
  "ok": true,
  "ready": true,
  "risk": "low",
  "stdout": "rabbit.service active",
  "stderr": "",
  "timedout": false,
  "truncated": false
}

The text block should stay compact. The structured output should carry the typed data.

Upload results use a smaller shape:

{
  "allow": true,
  "bytes": 12,
  "checksum": "9f3e...",
  "destination": "/srv/rabbit/current/config/app.toml",
  "duration": 84,
  "mode": "0644",
  "ok": true,
  "overwrite": false,
  "source": "config/app.toml"
}

Rabbit does not return the upload shell program, remote acknowledgement, or file contents.

Log strategy

Logs must be bounded by default.

{
  "host": "prod",
  "target": "app",
  "limit": 100
}

Rabbit caps limit at 500. Larger investigations should use repeated targeted calls, not one giant dump.

Search strategy

Search should be exact and scoped.

{
  "host": "prod",
  "target": "app",
  "query": "database timeout"
}

Rabbit runs fixed-string search only against an approved target path or host root path and caps returned output. It prefers rg and falls back to grep.

Execution output

Rabbit execution returns:

  • exit code
  • duration
  • stdout
  • stderr
  • truncation flag
  • timeout state

Rabbit keeps at most 12 KB of combined stdout and stderr in one result.

Large output belongs in files or resources, not directly in the conversation.

Breakglass uses the same output cap even when its execution timeout is longer.

Host load

Rabbit bounds work independently for each approved host.

  • up to four read calls may run together
  • uploads, restarts, and breakglass calls run exclusively
  • a waiting mutation runs before later reads
  • each host queue is capped at 32 requests
  • cancelled requests leave the queue immediately

This prevents one agent session from creating an unbounded SSH connection storm while keeping unrelated hosts independent.

Upload strategy

Upload arguments contain paths and intent, never file bytes.

Rabbit hashes a project-local file, streams it directly to OpenSSH stdin, verifies it remotely, and returns only bounded metadata. The current file limit is 16 MiB, but a successful transfer consumes roughly the same model context whether the file contains 12 bytes or 16 MiB.

Client behavior

Claude Code warns when MCP output exceeds 10,000 tokens and defaults to a 25,000 token maximum. Rabbit advertises a 20,000 character result threshold and keeps its own execution output smaller.

Codex reads Rabbit’s server instructions during MCP initialization. Rabbit keeps its complete safety workflow inside the first 512 characters.

OpenCode warns that enabled MCP servers consume context. Keep Rabbit enabled because it exposes a small fixed tool surface, then disable unrelated MCP servers that are not needed for the task.

Pi receives the same bounded results through native tools without loading an MCP bridge.

References