agenttoolbox-agent
v0.5.2
Published
Local agent bridge for AgentToolbox: pi-agent chat sessions for the free-models page, BYOK, loopback only
Readme
agenttoolbox-agent
Local agent bridge for AgentToolbox.
Run:
npx agenttoolbox-agent@latestStarts a loopback-only (127.0.0.1:31415) HTTP service that powers the Chat drawer on the free-models page. Bring your own API keys — they stay in memory and are sent only to the model provider you choose.
GET /health— liveness + version, adapter availability, granted directoriesPOST /sessions—{provider, model, baseUrl?, apiKey?, envKey?, name?, contextWindow?, maxOutput?, agents?, agent?, cwd?, toolTier?}→{sessionId, adapter, provider, model, cwd, toolTier}(agentsis the adapter preference order, e.g.["cc","pi"]; default["pi"].agenttargets one adapter directly — including a specific ACP agent viaagent: "acp:<id>".cwdis an optional working directory — it must exist and be a directory, or the session is rejected with400 {error: "invalid cwd"}.toolTierselects the tool set — see Work directory & tool tiers)POST /sessions/:id/messages—{text}→ SSE stream (delta/thinking/usage/activity/permission_request/turn_end/error/done)POST /sessions/:id/permission— answers a pendingpermission_request:{reqId, decision: "allow"}(any otherdecisioncounts as a denial) →200 {ok: true}. Unknown session or unknown/already-settledreqId→404(replays are treated as never having existed)POST /sessions/:id/abortPOST /quota/openrouter—{apiKey}→{usage, limit, isFreeTier, rateLimit}. Proxies OpenRouter's/api/v1/keyaccount lookup so the page can show free-tier quota.400without a key; the key is forwarded only to OpenRouter and never logged. Upstream failures mirror the upstream status code with a generic message — no response fragments leak through
Options: --port <n> / PORT env (default 31415); AGENT_ALLOWED_ORIGINS
(comma-separated extra CORS origins, e.g. http://localhost:4321 for dev).
Adapters
A session is served by the first available adapter in the preference order you
pass as agents. Two adapters ship today:
pi— in-process runtime (pi-agent-core). This is the default and has no extra prerequisites beyond Node; it talks to the provider directly with your key.cc— spawns theclaudeCLI headless (claude -p --output-format=stream-json) and streams its events back. It requires Claude Code to be installed and onPATH. Use it for models that are only served behind an agentic harness — e.g. OpenRouter's gated:freemodels, which reject plain API clients.
The cc adapter runs the CLI against a throwaway CLAUDE_CONFIG_DIR (a fresh
temp directory per turn), so it never loads or mutates your own
~/.claude/settings.json — your personal model, hooks, and plugins stay out of
the session.
acp — one adapter, five more agents
The acp adapter speaks the Agent Client Protocol
(JSON-RPC over stdio, official Node SDK) and exposes five coding agents:
| id (agent: "acp:<id>") | launched as | config isolation |
|---|---|---|
| acp:hermes | hermes acp | HERMES_HOME |
| acp:openclaw | openclaw acp | OPENCLAW_HOME — requires the OpenClaw Gateway to be running |
| acp:opencode | opencode acp | OPENCODE_CONFIG_DIR (+ OPENCODE_DISABLE_CLAUDE_CODE=1) |
| acp:codex | npx -y @zed-industries/codex-acp | CODEX_HOME |
| acp:dsh | npx -y @deepseek-ai/dsh --profile acp | DSH_HOME |
Each is listed in /health as acp:<id> with a live available probe — the
chat UI only offers agents it can actually run. Verified working: hermes
(full turn), codex (handshake). openclaw needs its Gateway; opencode and dsh
follow the same path once installed. ACP sessions are throwaway-configured too
(each spawn gets a fresh config home), and permission requests are answered for
headless use — heuristically in read tier (read-only-looking calls pass,
everything else is denied) and via the approval drawer in full tier (see
Work directory & tool tiers); the security
boundary remains the session cwd and the directory-grant mechanism.
Work directory & tool tiers
Sessions are chat-only by default. Two optional POST /sessions fields
unlock file access, both scoped to a single session:
cwd— a path on the machine running the gateway. It is validated at session creation (must exist and be a directory, else400 invalid cwd) and becomes the working directory / path boundary for every tool the agent gets.toolTier—'none'(default),'read', or'full'. Anything unrecognized falls back to'none', so a typo can never escalate privileges.'full'additionally unlocks the write/execute capabilities per adapter (see below), with every gated call approved through the drawer first (Permission flow). Note thatbashruns with the gateway's own environment (-lcalso sources login profiles), so approved commands can read the gateway process's environment variables.
What read means per adapter:
pi— attaches three read-only tools (read_file/list_dir/grep), built fresh each turn and closed over the sessioncwd. Every path is resolved and realpath-checked to land insidecwd(blocking../traversal and symlink escapes);grepskips dependency/hidden directories, caps single files at 1 MB and returns at most 50 matches.readwithout acwdattaches no tools at all — no hard boundary, no tools.cc— spawns the CLI with--allowedTools Read,Grep,Globand the working directory set (a previously granted directory takes precedence over the sessioncwd; see Directory grants). What the CLI may do with those tools is governed by Claude Code's own permission layer.acp— the sub-agent's session is created with the givencwd, andsession/request_permissioncalls are answered heuristically for headless use: inreadtier, tool calls whose title/kind looks read-only (read/view/grep/glob/search/list/find/inspect/ls) are allowed — answered with the request's own allow-once option, orcancelledwhen the offered options don't contain exactly one; write, execute, and unknown calls are rejected — as is everything innonetier.
What full means per adapter:
pi— attaches the read-only three pluswrite_fileandbash, again only when a sessioncwdis set (no boundary, no tools). Everywrite_file/bashcall is gated by an approval before it acts; a denial returns an error tool result (denied by user) and nothing is written or executed.write_filecreates or overwrites a text file up to 1 MB insidecwd; the target is resolved and realpath-checked, symlinks are never followed out ofcwd, and parent directories must already exist (nomkdir -p).bashrunsbash -lc <command>insidecwdas a detached process group with a 60s timeout (the whole group is killed) and merged stdout+stderr truncated to 8 KB; a non-zero exit code is reported as ordinary output, not a tool error.cc— the CLI is spawned with--input-format stream-json --permission-prompt-tool stdio, turning the pipe into a bidirectional control channel: every tool use the CLI attempts becomes acan_use_toolround-trip that the gateway forwards to the drawer and answers with allow (the call proceeds exactly as asked) or deny (adenied by usermessage back to the CLI). A missing or failing approval gate denies the call — fail-closed. Stdin is closed as soon as the turn's result line arrives, so the turn completes instead of waiting on the channel.acp—session/request_permissioncalls are forwarded to the approval drawer, with the action and summary derived from the tool call'skind/title. Inreadtier the drawer is bypassed (the heuristic above applies); a missing or failing approval gate rejects the call.
none is pure chat: no tools are attached and behavior is unchanged from
earlier releases.
Permission flow
A gated call pauses the turn: the gateway emits a permission_request SSE
event ({reqId, action, summary} — e.g. the command text or the file path
about to be written), the chat UI renders an approval card, and the turn
resumes only when you answer via POST /sessions/:id/permission. Unanswered
requests are auto-denied after 120 seconds; aborting the turn (or losing the
SSE connection) denies every pending request at once.
Denial is a first-class outcome — the agent is simply told the user refused
(pi: error tool result; cc: deny message to the CLI; acp: rejected
outcome) and can carry on from there. Nothing is ever auto-remembered: the
gateway never selects an "allow and remember" (*_always) option, and when an
ACP agent's offered permission options cannot be resolved to exactly one safe
allow-once and one reject-once entry (missing, duplicated, ambiguous, or
"remember"-only lists), the answer is cancelled — the agent is told the user
didn't choose, nothing is granted and nothing remembered.
SSE events
POST /sessions/:id/messages streams these SSE kinds:
delta{text}— assistant text (streamed)thinking{text}— reasoning chain (render it muted/italic; it arrives before the answer)usage{usage:{used,size,cost?}}— context-window accounting (ACP agents)activity{title}— a tool call started (e.g. "Read file")permission_request{reqId, action, summary}— a gated tool call needs approval; the turn pauses until you answer viaPOST /sessions/:id/permission(see Permission flow; unanswered requests auto-deny after 120s)turn_end{stopReason}— the turn finished (end_turn, orerroron failure)error{message}— something failed (bad key, gated model, adapter error)done— always the final frame; the stream closes after it
Directory grants
The cc adapter is chat-only by default: in none/read tiers, with no
granted directories and no session cwd, it is given no tools at all. Since
0.4.0 a session can unlock its read-only tools (Read/Grep/Glob)
per-session via the cwd/toolTier fields (see
Work directory & tool tiers); persistent grants
remain the alternative, and a granted directory takes precedence over the
session cwd for cc. full tier is deliberately different: the CLI keeps
its native tool set behind the per-action approval drawer, and with no cwd
(or grant) it works in the gateway process's own working directory — set
cwd explicitly if that is not what you want.
Browser pages cannot hand a local service a trusted filesystem path, so persistent grants are terminal-only:
npx agenttoolbox-agent grant <dir> # authorize a directory
npx agenttoolbox-agent list # show granted directories
npx agenttoolbox-agent revoke <dir> # withdraw a grantGrants persist in ~/.config/agenttoolbox/config.json. Point
ATBX_CONFIG_PATH at a different file to override that location (used by tests
and by the CLI subcommands).
HTTP responses
GET /health returns the service version, adapter availability, and the
current grant list:
{
"ok": true,
"version": "0.5.2",
"agents": [
{ "id": "pi", "available": true },
{ "id": "cc", "available": false },
{ "id": "acp:hermes", "name": "Hermes", "available": true },
{ "id": "acp:codex", "name": "Codex", "available": false }
],
"grantedDirs": ["/home/you/project"]
}agents[].available is a live probe (for cc, whether the claude binary is
found; for acp:<id>, whether that agent's CLI resolves). name is a display
label for ACP sub-agents.
Node >= 22.12. MIT.
