Skip to content

0018. Agent guardrails in AGENTS.md and configuration-only LLM gateway support

Status

Accepted

Context

The image was built around one AI agent, Claude Code. Two problems came up when users asked to reach models through an LLM gateway (LiteLLM) and to use agents other than Claude Code:

  • The safety rules only reached Claude. The rules against running terraform apply / destroy, packer build, or production ansible-playbook without confirmation lived only in CLAUDE.md. So did the rules on sudoers and on asking for op:// paths instead of secret values. Other agents read AGENTS.md, which had none of them. The firewall and proxy limit egress for every agent, but they cannot stop a destructive command against an allowed endpoint.
  • There was no supported way to point AI tools at a gateway. The templates only covered Anthropic's hosts.

Facts checked in the container while deciding:

  • privoxy does not restrict HTTPS CONNECT to port 443. A request to an allowlisted host on port 8443 was forwarded, not refused. A gateway on any port works.
  • mitmproxy (mitmdump, system Python) verifies upstream certificates against /etc/ssl/certs/ca-certificates.crt, because Alpine's certifi points there. Only root can update that bundle (through install-mitm-ca.sh), so a gateway behind a private CA cannot be trusted without an image change.
  • VS Code installs the extensions listed in devcontainer.json even though the container cannot reach the Marketplace: the server's own Marketplace requests are refused, and the extensions still install.
  • Token counting in mitm/addon.py and monitor/main.go only recognises Anthropic and GitHub Copilot hosts.

Decision

  1. AGENTS.md ## Agent Guardrails is the source of truth for the Never / Always / secrets-handling rules, for every agent. It sits in the BASE region, so update-project.sh syncs it into consuming projects. CLAUDE.md keeps an identical copy, marked as a mirror. This repo's own rules get the same treatment: AGENTS.md ### Agent Rules (this repo) mirrors CLAUDE.md ## Project Rules.
  2. LLM gateway support is configuration-only. The examples/ templates (and this repo's .devcontainer/) ship commented-out placeholders: the gateway hostname in both allowlists (with # tag:llm in proxy.yaml), plus OPENAI_BASE_URL / OPENAI_API_KEY in env.sh and devcontainer.json, read from 1Password. The image is unchanged and Claude Code stays installed. docs/how-to/use-llm-gateway.md documents the setup and its limits.

Rejected:

  • Moving the rules out of CLAUDE.md. Claude Code loads CLAUDE.md automatically but does not reliably follow its "read AGENTS.md first" instruction, so moving the rules would weaken Claude's guardrails.
  • Importing AGENTS.md into CLAUDE.md (@AGENTS.md). It would load the whole file (hundreds of lines) into every Claude session to deliver a list of about 20 rules.
  • Baking a second agent into the image, or making Claude Code optional at build time. Deferred. Both change the container contract, and the second doubles the build and release matrix. Revisit when a specific agent is chosen.

Consequences

Accepted:

  • Any agent that reads AGENTS.md gets the same guardrails as Claude Code.
  • A project can route AI traffic through a gateway, or use a non-Claude agent, by editing only its .devcontainer/ files. Leaving the Anthropic entries commented out (the template default) makes the gateway the only way to reach a model.
  • The rules exist in two places. Drift is possible; both files say which is the source of truth and ask for changes to be mirrored.

Known limits (would need image changes):

  • Gateway calls appear in the dashboard Proxy Feed but without token or cost counts.
  • A gateway behind a private CA cannot be used.
  • A command-line agent installed at runtime is lost on rebuild.