Spens logoSpens

What is Spens

The problem spens solves, how it works, and what a session looks like

Spens is a CLI toolchain for running AI coding agents — Codex, Claude Code, opencode, pi — inside sandboxed Docker containers with full network interception and audit control.

The problem

If you work across many codebases and experiment with multiple agents and LLMs, you need a way to run them without polluting your machine, creating conflicts, or having an agent blow away your home directory. Spens gives you reproducible, observable agent runtimes.

Three building blocks

Spens combines three tools into one runtime:

  1. Docker — isolated, ephemeral environments. Each session gets its own container.
  2. nono.sh — a sandboxed shell with filesystem access control and audit logging. The agent runs as a non-root user inside it.
  3. mitmproxy — an SSL-intercepting proxy that captures and decodes LLM API traffic.

What a session looks like

A spens session involves three cooperating pieces: a CLI process on the host, an interceptor container, and an agent container. The CLI orchestrates everything.

  1. Load inputs. The CLI resolves the environment and agent templates (built-in, local templates/, or a direct .json file), and reads .spens.config.json from the workspace if present.
  2. Generate the agent image. A Dockerfile is written from the templates: base image, system packages, the unprivileged agent user, the nono.sh installation, the agent's install command, and any static config files. The image is built with Docker.
  3. Generate the interceptor image. A second Dockerfile installs mitmproxy and the spens addon. The addon's runtime config is generated from .spens.config.json and baked into the image.
  4. Create the networks. Two Docker networks are created per session: an internal network holding the agent and the interceptor, and an egress network giving the interceptor (and only the interceptor) internet access.
  5. Start the interceptor. The interceptor container runs mitmdump with the spens addon on port 9090, plus a DNS forwarder on port 53. It generates its CA certificate on first run.
  6. Start the agent. The agent container joins the internal network, points its /etc/resolv.conf at the interceptor's DNS forwarder, and receives proxy environment variables plus the mitmproxy CA certificate. Its entrypoint waits for readiness markers from the interceptor, runs any pre_sandbox_commands, then starts the agent under nono run with the combined profile.
  7. Capture. Every byte the agent sends flows through the interceptor. LLM API calls are decoded and written to traces/captured.jsonl. Every request and response is logged to traces/request_log.jsonl. DNS queries are logged to traces/dns_log.jsonl. Domain rules are enforced fail-closed. Placeholders are swapped for real secrets only on matching hostnames, and real secret values are masked before anything is written to disk.
  8. Summarize. When the agent exits, the CLI computes a session summary (tokens, cost, models, file changes) from the captured traces and writes session_summary.json. The end-of-session prompt asks whether to keep the agent's workspace changes or roll them back.
  9. Persist. Everything is written under <workspace>/.spens/sessions/<session-id>/: events.jsonl, state.json, traces/, nono-audit/, and session_summary.json.

Security posture

Spens is honest about its limits. It is not designed to be 100 percent secure, because it uses Docker and containers share the host kernel. It is secure enough for most agent workloads, and it can be run with gVisor to harden the Docker instance if you need a stronger boundary.

For a technical explanation of the two-container setup, network enforcement, the interceptor, the nono sandbox, policy masking, and the session data model, see ARCHITECTURE.md in the repository.

On this page