Configure · package · govern · run · observe · 13 agent CLIs

Build, govern, and run
your agent stack.

Define servers, skills, instructions, settings, hooks, plugins, profiles, and secrets once. AgentStack turns that source of truth into native config across 13 agent CLIs, installs and pins capabilities, activates profiles per task, enforces machine-owned policy, runs agents under optional lockdown, and records the evidence you need to audit and optimize the stack.

clone → inert → trust → firewalled → logged
$ agentstack connect --all --write     # once: the gateway, in every CLI
$ git clone some-repo && cd some-repo  # declares servers you've never seen
  agent session starts…
  ✗ untrusted — no servers spawned, no secrets resolved
 
$ agentstack trust .                   # you review, then consent
  ▶ demo: runs `python3 ./server.py`
 trusted at sha256:945b4b…
 
  agent → demo.echo        ✓ ok      brokered, logged
  agent → demo.delete_all  ✗ denied  rule "!delete_*" — YOUR machine policy
 
  # both brokered outcomes are in the audit log

Why this exists

Your agents run whatever your repos tell them to.

Every skill, MCP server, and agent config you adopt is unreviewed code plus instructions, wired into a process that holds your credentials, your shell, and the network. Installing one is npm install with an agent attached — except with no lockfile, no review gate, and no record of what it did. Three gaps follow from that:

One clone away

A repo can declare MCP servers, and the moment an agent session starts, they want to run — commands you never read, with your keychain in reach.
Here, a clone is inert until you trust its exact bytes. Any edit re-gates it.

One prompt away

An injected instruction can steer a well-behaved agent into using a legitimate tool against you — deleting, leaking, phoning home.
Here, your machine policy — which no repo can loosen — fences tools, secrets, and egress; --lockdown removes the network route entirely.

Invisible afterwards

When something does go wrong, most setups can't answer the first question a reviewer asks: what did the agent actually call?
Here, every brokered call, denial, and secret touch lands in an audit log you can read back per run.

Honestly scoped

No tool can make agent use risk-free: hosts you allow stay allowed, and payloads aren't inspected.
That's why every mode carries an honest posture label — and if you run one agent with one hand-managed server, you may not need this yet. The moment capabilities come from repos you didn't write, you do.

Install

curl -fsSL https://raw.githubusercontent.com/Tarekkharsa/agentstack/main/install.sh | sh

One static binary, no runtime dependencies. From a checkout: cargo build --release && target/release/agentstack self link.

The trust gate

Repo-declared servers wait for your consent.

Register the gateway once (agentstack connect --all --write) and every repo you open brings its own servers — no files copied in. But a repo you haven't reviewed is inert: none of its MCP servers are spawned or contacted, no secrets resolved. You review what it declares, trust it (pinned to a consent digest of the manifest, local overlay, and lockfile, so any later edit re-gates it), and from then on every brokered call passes two firewall layers — the repo's [policy.tools], and your own machine-level rules that no repo can see, shadow, or loosen — and lands in the call log.

$ git clone some-repo && cd some-repo   # declares servers you've never seen
$ agentstack mcp --auto-project        # an agent asks: which project servers can I use?
  ✗ untrusted — none of its servers are spawned or contacted

$ agentstack trust .
  ▶ demo: runs `python3 ./server.py`   # you see exactly what it would run
 trusted at sha256:945b4b…         # editing the manifest re-gates it

  agent → demo.echo         ✓ ok       brokered through the gateway, logged
  agent → demo.secret_read  ✗ denied   repo [policy.tools] firewall
  agent → demo.delete_all   ✗ denied   YOUR machine policy — "*" = ["!delete_*"]
  every call → ~/.agentstack/audit/calls.jsonl  (tool · outcome · latency)
…and secrets never travel
$ agentstack lib sync   # your library syncs machine-to-machine over git
  ✗ refusing to sync — 'Authorization' looks like a literal secret
 make it a ${REF} and it travels safely — values never leave the keychain

Condensed from a real run — reproduce the whole gate: docs/trust-gate-demo.sh.

The whole story, in one binary: an untrusted repo's agents can touch nothing you didn't review, and every capability call is firewallable and logged (the documented limits say exactly what trust does and doesn't cover). Prefer a drop-in proxy? --transparent advertises every policy-filtered upstream tool straight in tools/list, so any standard MCP client works with zero agentstack knowledge — the default compact mode keeps them behind one search tool so agent context stays small.

The sandbox

Don't trust the agent? Take away the network.

The trust gate decides what an agent may do. agentstack run --sandbox decides what it can do: the harness runs in a container, its traffic routes through an egress proxy enforcing your machine [policy.egress], and every allow and block lands in a per-run flight recorder you read back with agentstack report. The workspace mounts read-only unless your [policy.filesystem] says otherwise — the kernel enforces the bind, not the harness.

--sandbox proxied · direct route open
The container gets an ordinary bridge network with its HTTPS_PROXY pointed at the agentstack proxy: configured egress is filtered and recorded. A process that ignores the proxy env still has a direct route — that caveat is what the next flag removes.
--lockdown no route out
The container's only network is an internal one whose single reachable peer is the egress-proxy sidecar. No host route, no internet, no DNS beyond it. An agent that ignores the proxy reaches nothing — the confinement is topology, not convention. Verified live on Docker through the real binary.
no route out · denied host blocked · on the record
$ cat ~/.agentstack/agentstack.toml   # YOUR machine firewall
  [policy.egress]
  "*" = ["!blocked.invalid"]           # no repo can loosen this
 
$ agentstack run --lockdown shtest -- -c 'unset HTTPS_PROXY; curl example.com'
  posture: LOCKDOWN / ENFORCED · NO DIRECT ROUTE
  🔒 no host route, no internet — the only peer is the egress sidecar
  BLOCKED                               # ignoring the proxy reaches nothing
 
$ agentstack run --lockdown shtest -- -c 'curl https://blocked.invalid/steal'
  ✗ refused at the sidecar — and recorded
 
$ agentstack report r-0859dcee73
  Posture   LOCKDOWN / ENFORCED · NO DIRECT ROUTE
  Egress
    ✗ shtest → blocked.invalid  denied by rule "!blocked.invalid" (machine policy)

Condensed from a real run. The full script is runnable (needs Docker): agentstack-test/demo-lockdown.sh.

The proxy is picky on purpose: hostnames are normalized before matching, a tunnel's TLS SNI must equal the CONNECT host (no domain fronting), any name resolving to a loopback, private, link-local, or cloud-metadata address is refused (no SSRF into your own network), and a per-run credential means only its own sandbox can use it. This boundary was swept by a two-reviewer security review — all five High and all seven Medium findings are closed; the remaining accepted debt is tracked. Honest scope: hosts you allow stay allowed — the claim is unapproved egress is blocked, never "exfiltration is impossible."

Experimental execution primitive

Let generated code compose tools—not inherit your machine.

A sandbox-enabled build can expose tools_execute: one bounded TypeScript program receives JSON input and only the exact MCP tools named in its grant. It runs non-root in a read-only, resource-limited Docker container with no workspace, credentials, package installation, or direct network route. Calls return through the same policy-enforcing gateway and appear beneath the execution in agentstack report.

machine-owned opt-in
# ~/.agentstack/agentstack.toml
[experimental]
tools_execute = true
 
[experimental.tools_execute_limits]
timeout_ms = 30000
max_calls = 40
max_output_bytes = 131072
 
allowTools = ["github__get_issue"]  # exact names, no wildcards
✓ x-… → github__get_issue      # gateway checked + recorded

Off by default, Docker-only, and never enabled by a repository manifest. A focused implementation review and regression-hardening pass completed on 2026-07-13. It remains experimental while executor-specific SBOM/attestation and longer-running soak evidence are outstanding. Read the exact contract and enforcement matrix.

What's enforced where

"Trusted" is not "confined." Here's the difference.

Trusting a project approves its current manifest, local overlay, and lockfile consent digest for automatic loading on this machine; lock verification separately catches pinned capability drift. What actually confines a run is its execution mode. Each mode carries a posture label, so a host run and a lockdown run never read the same:

host — HOST / ADVISORY
No container. The gateway still brokers tool calls, but nothing confines the process's own egress or filesystem.
gateway — GATEWAY / BROKERED
Every MCP call routes through the in-process broker: firewalled and audited, but the process runs on the bare machine.
--sandbox — SANDBOX / PROXIED · DIRECT ROUTE OPEN
Proxied HTTPS egress is checked against your compiled policy — but the container keeps an ordinary bridge, so a proxy-ignoring process can still dial out. --lockdown closes that route.
--lockdown — ENFORCED · NO DIRECT ROUTE
Container on an internal-only network whose sole peer is the egress sidecar. Enforced and topologically confined.
Derived from docs/ENFORCEMENT.md, which is checked against the source per cell.
Dimension hostADVISORY gatewayBROKERED --sandboxPROXIED --lockdownNO ROUTE
Tools unsupportedenforcedunsupportedunsupported
Egress coarsecoarseenforced *enforced
Secrets enforcedenforcedcoarsecoarse
Filesystem — write unsupportedunsupportedcoarsecoarse
Filesystem — read unsupportedunsupportedcoarsecoarse
Audit / recording unsupportedenforcedcoarsecoarse
enforced — a runtime mechanism prevents the disallowed action coarse — a real check runs, at coarser granularity than the policy expresses unsupported — no code path consults the policy here; no check happens

enforced * — for proxied traffic only: --sandbox's bridge network leaves a direct route a proxy-ignoring process could use; --lockdown removes it.

Read every "enforced" with the ceiling in mind: it means the disallowed action is prevented at runtime — never that the allowed action is safe. An enforced egress allowlist blocks hosts you didn't approve; it doesn't inspect payloads, and it permits traffic to every host you did approve (the model API included). agentstack restricts destinations and records decisions; it can't guarantee sensitive content never leaves through an allowed destination. The full per-cell reasoning — including where --sandbox's bridge network stops short of --lockdown's topological confinement — is in the enforcement matrix.

Portable config

The same reviewed file, rendered into every CLI

Everything above runs through the live gateway — zero files copied. When you want native config files instead (or as well), the same manifest renders them. And you don't start from a blank page: init reads the agent config already on your machine and turns it into a manifest you can review, commit, and share. (Nothing installed yet? init writes a commented starter manifest instead — no dead ends on a clean machine.)

Serves — and renders native config for —

Claude Code Codex CLI Cursor Windsurf Gemini CLI VS Code GitHub Copilot CLI Claude Desktop OpenCode Antigravity Junie Kiro Pi
  1. agentstack init

    Imports the servers and skills you already have; inline tokens become ${REF}s in your OS keychain.

  2. agentstack bootstrap

    Preflight: which CLIs are installed, which skills and secrets are missing, what would change.

  3. agentstack apply

    Preview each CLI's config diff, confirm to write. Writes are atomic, backed up, and tracked — restore undoes them.

  4. agentstack doctor

    Any time something feels off. Every warning names the exact fix command.

init → bootstrap → apply → every CLI in sync
$ agentstack init
  🔍 Detected 6 CLIs: Claude Code · Codex · Copilot · Gemini · OpenCode · Pi
  📥 Imported 1 MCP server from existing configs
  ✅ Wrote .agentstack/agentstack.toml
 
$ agentstack bootstrap
 manifest validates · 6 adapters installed · no missing secrets
 
$ agentstack apply --write
  Claude Code   ✓ up to date                    # it already had the server
  Codex CLI     + [mcp_servers.filesystem]      ✓ wrote 1 server
  Gemini CLI    + "filesystem": { … }           ✓ wrote 1 server
  …Copilot, OpenCode, Pi — the same server, each in its own syntax
 
$ agentstack apply
 nothing to write — every target in sync

Condensed from a real run. Reproduce it fenced (never touches your real configs): agentstack-test/demo-firstrun.sh.

The manifest

One file, reviewed like code

version = 1

[servers.github]
type = "http"
url = "https://api.githubcopilot.com/mcp/"
headers = { Authorization = "Bearer ${GH_PAT}" } # resolved per machine, never stored

[profiles.backend]
servers = ["github", "kibana"]
skills  = ["sql-review"]               # resolves from your central library

[targets]
default = ["claude-code", "codex"]

Secrets resolve locally — process env, varlock, OS keychain, or .env — and unresolved secrets block writes, so placeholders never leak into live config. A lockfile pins every skill and server by digest; install --locked reproduces the setup in CI.

Rendered files

Pick where artifacts live

You always commit the intent (manifest + lock). The rendered artifacts (.mcp.json, .claude/skills/) are a per-project choice:

static default
Artifacts (.mcp.json, .claude/skills/, and the compiled CLAUDE.md / AGENTS.md) sit on disk, kept out of git by a managed .gitignore block that only ever covers files agentstack itself wrote — so a repo tracks only your .agentstack/ intent and a hand-maintained file is never hidden. Works however you launch your tools.
clean-at-rest
Nothing generated exists between sessions. agentstack run <cli> --profile <p> injects on launch and reverts on exit; git status stays silent.
zero files
Nothing generated at all — the gateway serves each repo's servers on the fly, behind the trust gate above.

Teams & CI

Clone, bootstrap, done

git clone <repo>
agentstack bootstrap
agentstack secret set GH_PAT     # local only; never committed
agentstack apply --write

In CI, the trust gate is two commands — or the one-line GitHub Action:

agentstack install --locked   # fail if sources drifted from the pinned lock
agentstack doctor --ci        # fail on errors, drift, policy, unsafe content

Provenance, too: agentstack sign writes a detached ed25519 signature over the lockfile, and agentstack verify checks it — so CI can prove the pinned content is the content someone actually reviewed.

Going further

When you want more than the loop

Choose the right boundary

Our recommendation for composing profiles, delivery, trust, policy, isolation, and evidence—plus the cases where the default should change.

Decision guide

Central library

One managed home for skills and servers; projects select by name and stay digest-pinned. Install from a local dir or a git subdirectory, sync across machines, and pull ready-made skills from the shipped catalog.

Visual walkthrough

Usage insight

agentstack analyze reports what you actually call, from the runtime audit log, and flags library skills and servers you installed but never use — read-only and local, so pruning is data-driven.

Read-only & local

Feature reference

The complete tested inventory: vendor packs, MCP firewall, call audit log, optimize, plugin recipes, live runs, code mode, and the full command list.

Full reference

The no-terminal path

The dashboard lifecycle — discover, add, secrets, apply, verify, undo — from a local, token-gated dashboard, with read-only Proxy and Insights panels.

Dashboard guide

Vendor packs

agentstack add from git:github.com/acme/pack@v1.2.0 installs a versioned MCP + skills + house-rules bundle — policy-gated and content-scanned before anything is written.

Versioned by tag

Support any CLI, no rebuild

Each of the 13 agents is one data-driven YAML descriptor. Drop your own into ~/.agentstack/adapters/ to add or override a harness — validated first, and a broken drop-in is skipped with a warning, never fatal.

Data-driven adapters
pack @v1.0.0 → every CLI → firewalled → audited → @v1.1.0
$ agentstack add from git:acme/pack@v1.0.0 --write
  ✓ installed pack 'acme' — servers + skills, secrets stay ${REF}s
$ agentstack apply --write
 the whole pack spreads to every CLI on this machine
 
  acme__search_docs     ✓ ok
  acme__delete_index    ✗ refused   [policy.tools] rule "!delete_*"
$ agentstack audit --calls
  2 calls · 2 tools · 1 denied by policy — digests, never values
 
$ agentstack upgrade acme --yes --write   # vendor ships v1.1.0
  ✓ upgraded pack 'acme' — previewed, re-pinned @v1.1.0

The closed loop in under a minute, condensed from a real run: agentstack-test/demo-closed-loop.sh.