A Carranca photographed by Marcel Gautherot in 1946. Instituto Moreira Salles collection.
Isolated agent runtime with verified audits, deep observability, policy enforcement, and adversarial hardening. Named after the carved figureheads on boats in Brazil's São Francisco river, believed to protect sailors. Carranca protects engineers from coding agents by running them in hardened containers with tamper-evident logs, kernel-level tracing, enforceable guardrails, and forgery detection.
This page is the primary technical reference for Carranca. It combines the current architecture, configuration schema, CLI usage, session log format, trust model, roadmap, versioning policy, and changelog in one browsable document.
Carranca is a local agent runtime for engineers and teams that want coding agents to run inside operator-controlled containers with reviewable evidence and enforceable guardrails. Its objective is not just to make agent sessions visible after the fact. It is to make them easier to constrain, inspect, and reason about while they run: - with isolated agent execution through Podman or Docker - with tamper-evident session logs and signed exports - with policy controls for network, filesystem, runtime duration, and resource usage - with optional independent observation outside the agent's namespaces ## Current position Carranca already provides: - **Verified audit evidence**: HMAC-signed session logs, checksum hardening, exportable archives, and provenance-tagged events - **Deep observability**: shell-command capture, file mutation events, execve tracing, network connection logging, resource sampling, and secret-read monitoring - **Technical policy enforcement**: network filtering, resource limits, time-boxed sessions, read-only overlays for watched paths, and pre-commit policy hooks - **Adversarial hardening**: all capabilities dropped, read-only root filesystem, seccomp filtering, FIFO forgery detection, and an independent observer sidecar for cross-referencing agent-reported events against kernel-observed activity - **Operational isolation**: dedicated agent/logger/observer containers, fail-closed session shutdown, and per-project agent images and configuration This means Carranca is beyond a transparency-only wrapper. The current product is a local runtime for people who need coding agents to stay useful without becoming opaque. ## Who Carranca is for Carranca is a strong fit when the operator wants to keep control over the runtime rather than outsource that control to a hosted sandbox provider. - **Security and compliance teams**: need tamper-evident audit records and a tighter execution boundary around AI-assisted development - **Platform and developer-experience engineers**: want explicit resource, network, filesystem, and image controls around local agent workflows - **Engineering managers and tech leads**: need sessions to be reviewable, reproducible, and bounded by technical policy - **Regulated or client-sensitive teams**: need signed logs, bounded runtime behavior, and traceable operator activity - **CI/CD pipelines and automation**: need unattended agent tasks (code review, generation, analysis) to run inside the same sandbox with bounded execution, controlled secret exposure, and auditable session logs - **Security-conscious individual engineers**: want local coding agents with a smaller blast radius than a normal unrestricted shell session ## Who Carranca is not for Carranca is not the best fit when the main problem is something other than local, auditable, policy-aware agent execution. - Teams that want a fully managed cloud execution platform instead of operating agent containers on their own machines or infrastructure - Users who need strong non-Linux parity today; macOS and Windows support remain experimental, especially for file-event coverage - Teams looking for a generic browser, desktop, or VM sandbox rather than a code-in-repository runtime - Workflows that need formal remote attestation, centralized control planes, or fleet orchestration out of the box; those sit in Carranca's future ecosystem layer rather than the current runtime core - Users who mainly want faster remote compute or disposable preview environments and do not care much about local auditability or guardrails ## Comparison with other agent sandboxes Carranca fits a different operating model from most cloud-first agent sandboxes. - **Carranca vs hosted code sandboxes**: products such as E2B, Daytona, and Modal Sandboxes emphasize disposable remote environments, API-driven provisioning, and hosted execution. Carranca instead centers on operator-controlled local or self-hosted container execution with persistent repo context, signed local evidence, and repo-specific policy controls. - **Carranca vs simple container wrappers around an agent CLI**: Carranca adds a separate logger, optional observer sidecar, fail-closed logging, HMAC-based tamper detection, and policy enforcement rather than only "run the agent in a container." - **Carranca vs general-purpose VM or browser isolation**: Carranca is narrower and more opinionated. It is optimized for coding agents working inside a real repository, not for broad desktop isolation or remote browsing tasks. The tradeoff is deliberate: Carranca gives up some convenience and cloud-scale abstractions in exchange for stronger operator control over a local coding workflow. ## Personas That See Value Concrete `.carranca.yml` and `.carranca/Containerfile` examples for these personas live under [examples/](examples/). - **A platform engineer** hardens agent access to production deployment repos with network allow-lists, read-only secret paths, and session duration caps - **A security engineer** reviews signed session logs and cross-references them against observer events during an incident or policy exception review - **A regulated-team lead** needs AI-assisted changes to remain attributable and reviewable before code enters a controlled delivery process - **A consultant working on client code** wants local agent assistance without giving a third-party hosted sandbox direct access to the client repository - **An open-source maintainer** wants external agent-generated patches to be reviewable with more context than a bare git diff - **A forensic analyst or incident responder** replays prior sessions from log exports to understand what an agent ran, touched, or attempted to access - **A CI pipeline operator** runs automated agent tasks (PR review, code generation, analysis) inside carranca with bounded execution, secret isolation, and session evidence as CI artifacts ## What remains ahead The remaining roadmap is about ecosystem and integration work rather than core runtime capabilities. See [roadmap.md](roadmap.md) for planned future work.
## Overview
Carranca runs coding agents inside containers with host isolation and structured
session logging. There is no compose layer; the CLI talks directly to a
supported container runtime command.
Today the supported runtime engines are:
- `podman`
- `docker`
Runtime selection precedence is:
1. `CARRANCA_CONTAINER_RUNTIME`
2. `.carranca.yml` `runtime.engine`
3. `auto` detection, which prefers Podman and falls back to Docker
Projects are configured through `.carranca.yml` using an ordered `agents:` list.
The first configured agent is the default execution target for `run` and
`config`, and `--agent <name>` selects a different configured agent explicitly.
Session ids are 16-char hex values and name all ephemeral runtime resources for a
single run: the agent container, logger container, FIFO tmpfs volume, and
transient images.
```
carranca run
│
├── <runtime> build (logger image from runtime/Containerfile.logger)
├── <runtime> build (agent image from .carranca/Containerfile)
├── <runtime> volume create (shared tmpfs for FIFO)
│
├── <runtime> run -d (logger)
│ ├── creates FIFO on shared volume
│ ├── starts inotifywait (or fswatch fallback) on /workspace (read-only)
│ ├── validates FIFO events for forgery indicators
│ ├── reads FIFO events
│ └── writes JSONL, checksum, and HMAC key files to /state/
│
├── <runtime> run -d (observer, optional)
│ ├── runs with --pid=host and CAP_SYS_PTRACE
│ ├── finds agent PID via cgroup matching
│ ├── runs strace for independent execve tracing
│ ├── polls /proc/<pid>/net/tcp for network connections
│ └── writes events to FIFO with source: "observer"
│
└── <runtime> run -it (agent)
├── --cap-drop ALL, --read-only, seccomp profile
├── shell-wrapper opens FIFO
├── writes shell events to FIFO
├── heartbeat every 30s
└── runs agent command interactively
```
## Session lifecycle
A `carranca run` session owns a small set of runtime resources:
- `carranca-<session>-agent` container
- `carranca-<session>-logger` container
- `carranca-<session>-observer` container (when `observability.independent_observer: true`)
- `carranca-<session>-fifo` tmpfs volume
- `carranca-<session>-agent` and `carranca-<session>-logger` transient images
Lifecycle is explicit:
1. Carranca computes a fresh `session_id`
2. It builds the transient logger and agent images
3. It creates the shared FIFO volume
4. It starts the logger container
5. It starts the observer sidecar (if independent observer is enabled)
6. It starts the interactive agent container
7. On normal exit, `SIGINT`, `SIGTERM`, or `carranca kill`, it stops the agent,
stops the observer, stops the logger gracefully so it can flush
cross-referencing results and `logger_stop`, then removes the FIFO volume
and transient images
This teardown path is idempotent. Interrupted interactive sessions should not
leave the logger container behind.
## Session management commands
Carranca exposes two complementary session-management commands:
- `carranca status` shows active sessions for the current repo and recent logs
- `carranca kill` stops either one exact session (`--session <id>`) or all
active sessions globally after confirmation
`status` is repo-scoped because logs are stored under the current repo's
`repo_id`. `kill` is global because active container resources are not tied to
the current working directory once they are running.
## Containers
Two or three containers share a tmpfs volume containing a Unix FIFO:
**Logger** (`runtime/Containerfile.logger`):
- Managed by carranca, not user-configurable
- Alpine-based, minimal (~7MB)
- Mounts: FIFO volume (rw), workspace (ro), state dir (rw)
- Capabilities: requests `CAP_LINUX_IMMUTABLE` for `chattr +a`; rootless Podman
degrades to `--userns keep-id`, so append-only is best-effort there too
- Owns the per-session `.jsonl`, `.checksums`, and `.hmac-key` files used by
`carranca log --verify` and `carranca log --export`
**Agent** (`.carranca/Containerfile`):
- User-configurable per project
- Copied to `.carranca/` on `carranca init`
- The user installs their agent CLI, language runtimes, tools
- Mounts: FIFO volume (rw), workspace (rw), optional cache dir (rw), optional custom volumes, repo-local Carranca skills during `run`, repo-local user skills, and install-managed Carranca skills during `config`
- Runs as the invoking host UID:GID on Linux, or `--userns keep-id` on rootless Podman, so bind-mounted workspace writes keep usable host ownership
- Hardened by default: `--cap-drop ALL`, `--read-only` root filesystem, seccomp profile blocking dangerous syscalls
- The shell wrapper is always injected as the entrypoint
- When fine-grained network policies are active (`runtime.network` object form),
the entrypoint is overridden to `network-setup.sh` which applies iptables rules
before exec-ing the shell wrapper
**Observer** (reuses `runtime/Containerfile.logger`, optional):
- Launched when `observability.independent_observer: true`
- Runs with `--pid=host` and `CAP_SYS_PTRACE`
- Shares the FIFO volume and state directory but no namespace with the agent
- Finds the agent's host PID via cgroup matching, runs strace independently
- Polls `/proc/<pid>/net/tcp` for network connections
- Writes events to FIFO with `source: "observer"`
- Exits when the agent PID disappears
## Data flow
```
Agent container Logger container
┌──────────────────┐ ┌──────────────────────┐
│ shell-wrapper.sh │ │ logger.sh │
│ │ │ │ │ │
│ ├─ agent cmd │ FIFO │ ├─ read FIFO ──────┤──► session.jsonl
│ ├─ heartbeat ──┼──────►──────┼───┤ │ + .checksums
│ └─ exit code │ (tmpfs) │ ├─ validate event │
│ │ ▲ │ ├─ inotifywait ────┤ (or fswatch)
│ --cap-drop ALL │ │ │ ├─ HMAC chain ─────┤──► .hmac-key
│ --read-only │ │ │ └─ cross-reference │
│ seccomp profile │ │ │ /workspace (ro) │
│ /workspace (rw) │ │ └──────────────────────┘
└──────────────────┘ │
│
Observer container │ (optional, --pid=host)
┌──────────────────┐ │
│ observer.sh │ │
│ │ │ │
│ ├─ strace ─────┼──────┘
│ ├─ /proc/net ──┼──────┘
│ │ │
│ CAP_SYS_PTRACE │
│ no shared ns │
└──────────────────┘
```
## Directory layout
| Path | Mutable | Owner | Purpose |
|------|---------|-------|---------|
| `~/.local/share/carranca/` | No | Install | CLI, runtime assets, templates, and shipped skills |
| `~/.local/state/carranca/sessions/<repo-id>/` | Yes | Carranca | Session JSONL logs plus per-session `.checksums`, `.hmac-key`, `.tar`, and `.tar.sig` files |
| `~/.local/state/carranca/config/<repo-id>/` | Yes | Carranca | Config workflow proposals and audit history |
| `~/.local/state/carranca/cache/<repo-id>/home/` | Yes | Agent | Persistent agent home dir mounted at `/home/carranca` (auth, config, history) |
| `~/.config/carranca/config.yml` | Yes | User | Optional user-wide defaults for `runtime.*`, `volumes.*`, `observability.*`, and `policy.*` |
| `.carranca.yml` | Yes | User | Per-project configuration, including the ordered `agents:` list |
| `.carranca/Containerfile` | Yes | User | Agent container definition |
| `.carranca/shell-wrapper.sh` | No | Carranca | Injected into agent image at build |
| `.carranca/skills/carranca/` | Yes | User/Repo | Repo-local Carranca workflow skills scaffolded by `init` and mounted by `run` when present |
| `.carranca/skills/user/` | Yes | User | Per-project user-authored skills |
## Repo identity
`repo_id = sha256(git remote get-url origin)[:12]`
Falls back to `sha256(realpath(.))[:12]` for repos without a remote. Two repos
with the same name at different paths get distinct IDs. Moving a repo orphans
old sessions (documented, not a bug).
## What Carranca reads
Carranca currently reads configuration from:
- `.carranca.yml` in the project root
- `.carranca/Containerfile` in the project root
- `CARRANCA_CONTAINER_RUNTIME` as an environment override for runtime selection
- `~/.config/carranca/config.yml` for user-wide runtime and volume defaults (overridden by project config)
## Global config
Carranca reads user-wide defaults from:
```text
~/.config/carranca/config.yml
```
Override the directory with `CARRANCA_CONFIG_DIR`.
Only `runtime.*`, `volumes.*`, `observability.*`, and `policy.*` settings are read from global config.
Project-level `.carranca.yml` always takes precedence. Lists (like
`runtime.cap_add` and `volumes.extra`) are not merged — the project list
replaces the global list entirely when present.
Example global config:
```yaml
runtime:
engine: podman
network: false
cap_add:
- SYS_PTRACE
volumes:
cache: true
extra:
- ~/.ssh:/home/carranca/.ssh:ro
```
## `.carranca.yml`
Carranca supports only the ordered `agents:` format for project config.
```yaml
agents:
- name: codex
adapter: codex
command: codex
runtime:
engine: auto
network: true
# extra_flags: --gpus all
# logger_extra_flags:
volumes:
cache: true
# extra:
# - ~/.ssh:/home/carranca/.ssh:ro
# - ~/docs:/reference:ro
policy:
docs_before_code: warn
tests_before_impl: warn
watched_paths:
- .env
- secrets/
- "*.key"
observability:
resource_interval: 10
execve_tracing: false
network_logging: false
network_interval: 5
secret_monitoring: false
independent_observer: false
# orchestration:
# mode: pipeline # pipeline | parallel
# workspace: isolated # isolated | shared
# merge: carry # carry | discard (pipeline only)
# environment:
# passthrough:
# - ANTHROPIC_API_KEY
# env_file: .env.carranca
# vars:
# REGION: us-east-1
```
### Field reference
| Field | Required | Default | Current behavior |
|-------|----------|---------|------------------|
| `agents` | Yes | — | Ordered configured agents; the first entry is the default for `run` and `config` |
| `agents[].name` | Yes | — | Stable selector used by `--agent <name>` |
| `agents[].command` | Yes | — | Command executed inside the agent container |
| `agents[].adapter` | No | `default` | Adapter selection: `default`, `claude`, `codex`, `opencode`, or `stdin` |
| `runtime.engine` | No | `auto` | Runtime engine: `auto`, `docker`, or `podman` |
| `runtime.network` | No | `true` | Boolean `true`/`false` or object with `default`/`allow` keys. `false` adds `--network=none`. Object form enables fine-grained network filtering via iptables |
| `runtime.network.default` | No | — | Network policy default: must be `deny`. Presence of this key switches to filtered mode with iptables OUTPUT DROP + allow-list. Requires yq |
| `runtime.network.allow` | No | — | List of `host:port` entries allowed through the firewall (e.g., `*.anthropic.com:443`). Requires yq |
| `runtime.extra_flags` | No | — | Extra flags appended to the agent container `run` command |
| `runtime.logger_extra_flags` | No | — | Extra flags appended to the logger container `run` command |
| `runtime.seccomp_profile` | No | `default` | Seccomp profile for agent container. `default` uses carranca's built-in profile blocking dangerous syscalls (ptrace, mount, unshare, etc.). `unconfined` disables seccomp. Absolute path for custom profile. Linux only |
| `runtime.apparmor_profile` | No | — | AppArmor profile name for agent container. Must be pre-loaded via `apparmor_parser -r`. `unconfined` to disable. Empty (default) uses runtime default. Linux only |
| `runtime.cap_drop_all` | No | `true` | When `true`, drops all Linux capabilities from the agent container via `--cap-drop ALL`. `cap_add` becomes a strict allowlist applied after the drop |
| `runtime.read_only` | No | `true` | When `true`, runs agent container with `--read-only` root filesystem. `/tmp`, `/var/tmp`, `/run` get tmpfs mounts. `/workspace`, `/fifo`, and cache home are unaffected. When cache is disabled, `/home/carranca` gets a tmpfs mount |
| `runtime.cap_add` | No | — | List of Linux capabilities added to the agent container via `--cap-add`. When `cap_drop_all` is `true` (default), these are the only capabilities the agent has |
| `volumes.cache` | No | `true` | Persists `/home/carranca` under `~/.local/state/carranca/cache/<repo-id>/home/` |
| `volumes.extra` | No | — | Extra bind mounts added only to the agent container |
| `policy.docs_before_code` | No | — | `warn`, `enforce`, or `off`. When `warn` or `enforce`, injects git pre-commit hooks. `enforce` blocks commits that modify code without documentation |
| `policy.tests_before_impl` | No | — | `warn`, `enforce`, or `off`. When `warn` or `enforce`, injects git pre-commit hooks. `enforce` blocks commits that modify implementation without tests |
| `policy.max_duration` | No | — | Seconds; logger removes FIFO after this wall-clock limit, triggering agent fail-closed exit. `0` or absent means no limit |
| `policy.resource_limits.memory` | No | — | Container memory limit (e.g., `2g`, `512m`). Passed as `--memory` to agent container. Requires yq |
| `policy.resource_limits.cpus` | No | — | CPU limit (e.g., `2.0`). Passed as `--cpus` to agent container. Requires yq |
| `policy.resource_limits.pids` | No | — | Max number of processes. Passed as `--pids-limit` to agent container. Requires yq |
| `policy.filesystem.enforce_watched_paths` | No | `false` | When `true`, `watched_paths` directories and files are bind-mounted read-only. Requires yq |
| `watched_paths` | No | — | File events matching watched patterns are tagged with `"watched":true` in session logs |
| `observability.resource_interval` | No | `10` | Seconds between cgroup resource samples; `0` disables |
| `observability.execve_tracing` | No | `false` | Enable strace-based execve tracing in the logger; adds `CAP_SYS_PTRACE` to logger. Not required when `independent_observer` is enabled (the observer always traces) |
| `observability.network_logging` | No | `false` | Enable `/proc/net/tcp` polling for outbound connections; requires PID namespace sharing |
| `observability.network_interval` | No | `5` | Seconds between network connection polls |
| `observability.secret_monitoring` | No | `false` | Enable fanotify-based file read monitoring on `watched_paths`; adds `CAP_SYS_ADMIN` to logger |
| `observability.independent_observer` | No | `false` | Run execve tracer and network monitor in an independent sidecar container outside the agent's PID/mount namespace. Observer events are authenticated via a shared token on `/state/` (inaccessible to the agent). Always enables execve tracing regardless of `execve_tracing` setting. Cross-references events at session end as a best-effort heuristic |
| `orchestration.mode` | No | — | Multi-agent mode: `pipeline` (sequential, fail-fast) or `parallel` (concurrent). Requires 2+ agents. See [multi-agent.md](multi-agent.md) |
| `orchestration.workspace` | No | `isolated` | Workspace isolation: `isolated` (per-agent copy) or `shared` (single mount) |
| `orchestration.merge` | No | `carry` | Pipeline workspace merge: `carry` (next agent sees previous changes) or `discard` (each agent gets original workspace) |
| `environment.passthrough` | No | — | List of host environment variable names to forward to the agent container. Only vars that exist in the host env are passed; missing vars are skipped with a warning |
| `environment.env_file` | No | — | Path to a `.env` file on the host. Supports `~` expansion. Standard format: `KEY=VALUE`, optional `export` prefix, `#` comments, quoted values. File must exist if configured |
| `environment.vars` | No | — | Map of environment variables defined directly in the config (e.g., `MY_VAR: value`). Requires yq for map parsing; awk fallback supports simple `key: value` pairs |
### Runtime resolution
Runtime selection precedence is:
1. `CARRANCA_CONTAINER_RUNTIME`, if set
2. `.carranca.yml` `runtime.engine`, if set
3. `auto`, which detects a local runtime and prefers Podman before Docker
If `runtime.engine` or `CARRANCA_CONTAINER_RUNTIME` is set to an unsupported
value, Carranca fails fast.
### Agent adapters
Carranca resolves the effective driver for an agent like this:
- `adapter: claude` uses the configured command in Claude-style interactive mode
- `adapter: codex` uses the configured command in Codex-style interactive mode
- `adapter: opencode` uses the configured command in OpenCode-style interactive mode
- `adapter: stdin` pipes the generated prompt to the command on stdin
- `adapter: default` infers `claude`, `codex`, or `opencode` only when the command itself
starts with `claude`, `codex`, or `opencode`; otherwise it falls back to `stdin`
### Starter agents
`carranca init --agent <name>` only scaffolds supported starters:
- `codex`
- `claude`
- `opencode`
The generated config still uses the general `agents:` format, so you can add or
rename agents afterward.
### Examples
Claude:
```yaml
agents:
- name: claude
adapter: claude
command: claude
```
Codex:
```yaml
agents:
- name: codex
adapter: codex
command: codex
```
OpenCode:
```yaml
agents:
- name: opencode
adapter: opencode
command: opencode
```
Custom stdin-driven agent:
```yaml
agents:
- name: shell
adapter: stdin
command: bash /usr/local/bin/my-agent.sh
```
Podman plus GPU flags:
```yaml
agents:
- name: gpu-agent
adapter: stdin
command: my-agent
runtime:
engine: podman
extra_flags: --gpus all
```
No network:
```yaml
agents:
- name: codex
adapter: codex
command: codex
runtime:
network: false
```
Multiple agents and extra mounts:
```yaml
agents:
- name: codex
adapter: codex
command: codex
- name: claude
adapter: claude
command: claude
volumes:
extra:
- ~/.ssh:/home/carranca/.ssh:ro
- ~/projects/shared-lib:/reference/shared-lib:ro
```
Ephemeral home directory:
```yaml
agents:
- name: codex
adapter: codex
command: codex
volumes:
cache: false
```
Agent with extra capabilities:
```yaml
agents:
- name: debugger
adapter: stdin
command: my-debugger
runtime:
cap_add:
- SYS_PTRACE
```
Agent with environment variables:
```yaml
agents:
- name: claude
adapter: claude
command: claude
environment:
# Forward these host env vars into the agent container
passthrough:
- ANTHROPIC_API_KEY
- GITHUB_TOKEN
# Load additional vars from a .env file
env_file: .env.carranca
# Define vars directly (lowest priority for duplicates)
vars:
REGION: us-east-1
LOG_LEVEL: debug
```
Priority when the same variable appears in multiple sources: `vars` overrides `env_file` overrides `passthrough`.
## Configuration examples
Persona-specific examples live under [examples/](examples/). Each persona from
[objective.md](objective.md) has its own directory with:
- `.carranca.yml`
- `.carranca/Containerfile`
- a `README.md` describing the operating context and why Carranca helps
Start with [examples/README.md](examples/README.md), then open the persona that
matches your operating model:
- [examples/platform-engineer/](examples/platform-engineer/)
- [examples/security-engineer/](examples/security-engineer/)
- [examples/regulated-team-lead/](examples/regulated-team-lead/)
- [examples/consultant-client-code/](examples/consultant-client-code/)
- [examples/open-source-maintainer/](examples/open-source-maintainer/)
- [examples/forensic-analyst/](examples/forensic-analyst/)
- [examples/ci-reviewer/](examples/ci-reviewer/)
## `carranca config`
`carranca config` uses the selected configured agent to inspect the workspace and
propose updates to:
- `.carranca.yml`
- `.carranca/Containerfile`
It does not let the agent edit the workspace directly. Instead it requires the
agent to write complete proposed files into a proposal directory under:
```text
~/.local/state/carranca/config/<repo-id>/<session-id>/proposal/
```
The config prompt requires the agent to:
- read `/carranca-skills/confiskill/SKILL.md`
- inspect user skills under `/user-skills/`
- write rationale and detected stack summaries alongside the proposal
Mount behavior differs from `carranca run`:
- Carranca-managed skills come from the Carranca install and are mounted at
`/carranca-skills`
- user-managed skills come from `.carranca/skills/user/` when present and are
mounted at `/user-skills`
- the workspace is mounted read-only
`carranca config` uses the same TTY rule as `run`: `-it` when stdin is a TTY,
`-i` otherwise.
Useful commands:
```bash
carranca config
carranca config --agent claude
carranca config --prompt "install repo dev tools"
carranca config --dangerously-skip-confirmation
```
By default, `config` is propose-only until you confirm. It prints:
- detected workspace profile
- rationale for the proposal
- unified diffs for `.carranca.yml` and `.carranca/Containerfile`
Audit events for proposal rejection, bypassed confirmation, and applied changes
are recorded in:
```text
~/.local/state/carranca/config/<repo-id>/history.jsonl
```
## Cache volume
When `volumes.cache` is `true`, Carranca bind-mounts a repo-scoped home
directory into the agent container:
| Host path | Container path | Purpose |
|-----------|---------------|---------|
| `~/.local/state/carranca/cache/<repo-id>/home/` | `/home/carranca` | Agent auth, config, history, and other CLI home-state |
This cache is used by both `run` and `config` when enabled.
## Custom volumes
`volumes.extra` accepts bind mount entries in `host:container[:mode]` form. The
host path expands `~` to `$HOME`.
Examples:
- `~/.ssh:/home/carranca/.ssh:ro`
- `~/docs:/reference:ro`
- `~/data:/data:rw`
These extra mounts are added only to the agent container used by `run`. They are
not mounted into the logger, and `config` currently uses its own fixed mount set.
## `.carranca/Containerfile`
`carranca init` creates `.carranca/Containerfile`. You own the dependency and
tool installation steps between the marker comments; Carranca depends on the
shell-wrapper block at the bottom remaining intact.
```Dockerfile
FROM alpine:3.21
RUN apk add --no-cache \
bash \
coreutils \
curl \
git \
ca-certificates \
iptables
RUN mkdir -p /home/carranca && chmod 0777 /home/carranca
# Your agent and project dependencies here
RUN apk add --no-cache nodejs npm
# Carranca shell wrapper (do not remove)
COPY lib/json.sh /usr/local/bin/lib/json.sh
COPY shell-wrapper.sh /usr/local/bin/shell-wrapper.sh
RUN chmod +x /usr/local/bin/shell-wrapper.sh
WORKDIR /workspace
ENTRYPOINT ["/usr/local/bin/shell-wrapper.sh"]
```
`carranca config` validates that proposed Containerfiles still contain:
- `COPY lib/json.sh /usr/local/bin/lib/json.sh`
- `COPY shell-wrapper.sh /usr/local/bin/shell-wrapper.sh`
- `ENTRYPOINT ["/usr/local/bin/shell-wrapper.sh"]`
## `.carranca/skills/`
`carranca init` scaffolds two repo-local skill directories:
- `.carranca/skills/carranca/`
- `.carranca/skills/user/`
Current behavior:
- `carranca run` mounts `.carranca/skills/carranca/` to `/carranca-skills` when
present
- `carranca run` mounts `.carranca/skills/user/` to `/user-skills` when present
- `carranca config` mounts install-managed Carranca skills from
`~/.local/share/carranca/skills/` to `/carranca-skills`
- `carranca config` mounts `.carranca/skills/user/` to `/user-skills` when
present
That split lets `config` reliably use the shipped `confiskill` while still
honoring repo-local user skills.
`page/index.html` is the primary technical reference. This document is the
task-oriented CLI guide: what each command does, which flags it accepts, and
how operators typically use it.
## Command summary
| Command | Purpose |
|---------|---------|
| `carranca init` | Scaffold `.carranca.yml` and `.carranca/Containerfile` for a supported starter agent |
| `carranca config` | Ask a configured agent to propose container and config updates for the current repo |
| `carranca run` | Start an interactive agent session in the configured runtime |
| `carranca log` | Inspect, filter, verify, export, or timeline-render session logs |
| `carranca diff` | Compare two session logs across multiple dimensions |
| `carranca status` | Show active sessions and recent logs for the current repository |
| `carranca kill` | Stop one active session or all active sessions after confirmation |
| `carranca help` | Show top-level or command-specific help |
## Global help
```text
Usage: carranca <command>
carranca help <command>
```
Carranca also accepts `carranca <command> help` as command-specific help.
## `carranca init`
```text
Usage: carranca init [--agent <name>] [--force]
```
Scaffolds the project-local Carranca files in the current directory.
Options:
- `--agent <name>`: supported starters are `codex`, `claude`, and `opencode`
- `--force`: overwrite an existing `.carranca.yml` or `.carranca/Containerfile`
Typical use:
```bash
carranca init --agent codex
```
## `carranca config`
```text
Usage: carranca config [--agent <name>] [--prompt <text>] [--dangerously-skip-confirmation]
```
Runs the selected configured agent in config mode. The agent inspects the
workspace and proposes updates to `.carranca.yml` and `.carranca/Containerfile`
instead of editing the workspace directly.
Options:
- `--agent <name>`: choose a configured agent other than the first/default entry
- `--prompt <text>`: pass operator intent into the config workflow prompt
- `--dangerously-skip-confirmation`: apply the proposed diff without the final prompt
Typical use:
```bash
carranca config --prompt "install claude and add uv to the image"
```
## `carranca run`
```text
Usage: carranca run [--agent <name>] [--trust-repo-flags] [--timeout <seconds>]
```
Starts an interactive session for the selected configured agent. Carranca
builds the transient logger and agent images, starts the logger container, and
then launches the agent container with the shell wrapper and configured policy
controls.
Options:
- `--agent <name>`: run a named configured agent instead of the default first entry
- `--trust-repo-flags`: skip validation of `runtime.extra_flags` and `runtime.logger_extra_flags`
- `--timeout <seconds>`: maximum session duration in seconds. When both
`--timeout` and `policy.max_duration` are set, the minimum wins.
Exit codes:
| Code | Meaning |
|------|---------|
| 0 | Agent succeeded, no policy violations |
| 1–125 | Agent exit code (pass-through) |
| 71 | Logger lost — audit trail interrupted (EX_OSERR) |
| 124 | Session timed out (max_duration exceeded) |
Typical use:
```bash
carranca run --agent codex
carranca run --agent codex --timeout 600
```
## `carranca log`
```text
Usage: carranca log [--session <exact-id>] [--files-only] [--commands-only] [--top <n>]
```
Prints the latest session log for the current repository by default. The same
command also exposes integrity verification, export, and timeline rendering.
Options:
- `--session <id>`: inspect a specific session by exact id
- `--files-only`: print only touched file paths
- `--commands-only`: print only captured commands
- `--top <n>`: limit the top-touched-path summary
- `--verify`: verify HMAC chain integrity and detect tampering
- `--export`: create a signed archive (`.tar` and `.tar.sig`) next to the session log
- `--timeline`: render a compact ASCII timeline of session events
Typical uses:
```bash
# latest session for this repo
carranca log
# exact session
carranca log --session abc12345
# integrity check
carranca log --verify --session abc12345
# event timeline
carranca log --timeline --session abc12345
```
## `carranca diff`
```text
Usage: carranca diff <session-a> <session-b> [--pretty] [--repo-a <id>] [--repo-b <id>]
```
Compares two session logs across multiple dimensions: duration, agent,
commands, files touched, resource usage, network activity, and policy
violations.
Options:
- `--pretty`: human-readable formatted output (default is compact tab-separated)
- `--repo-a <id>`: repository id for session A (default: current repo)
- `--repo-b <id>`: repository id for session B (default: current repo)
Typical uses:
```bash
# compact comparison of two sessions
carranca diff abc12345 def67890
# human-readable comparison
carranca diff abc12345 def67890 --pretty
# cross-repo comparison
carranca diff abc12345 def67890 --repo-a aaa111 --repo-b bbb222
```
## `carranca status`
```text
Usage: carranca status [--session <exact-id>]
```
Shows active sessions and the five most recent session logs for the current
repository. With `--session`, it switches to a detailed view for one exact
session id.
Options:
- `--session <id>`: show detailed status for one exact session
Typical uses:
```bash
# repo overview
carranca status
# one session
carranca status --session abc12345
```
## `carranca kill`
```text
Usage: carranca kill [--session <exact-id>]
```
Stops active Carranca sessions after confirmation. Without `--session`, the
command targets all active sessions globally.
Options:
- `--session <id>`: stop one exact session after confirmation
Typical uses:
```bash
# stop one session
carranca kill --session abc12345
# stop every active session
carranca kill
```
## Operator workflows
### Bootstrap a repository
```bash
carranca init --agent codex
carranca config --prompt "install project dev tools"
carranca run --agent codex
```
### Review a completed session
```bash
carranca status
carranca log --session abc12345
carranca log --timeline --session abc12345
carranca log --verify --session abc12345
```
### Enforce a tighter runtime
Define the policy in `.carranca.yml`, then run normally:
```yaml
runtime:
network:
default: deny
allow:
- registry.npmjs.org:443
# Hardening defaults (shown for clarity)
cap_drop_all: true
read_only: true
seccomp_profile: default
policy:
docs_before_code: enforce
tests_before_impl: warn
max_duration: 1800
resource_limits:
memory: 2g
cpus: "2.0"
pids: 256
filesystem:
enforce_watched_paths: true
observability:
independent_observer: true
execve_tracing: true
```
```bash
carranca run --agent codex
```
## Related docs
- [page/index.html](page/index.html): primary technical reference
- [ci.md](ci.md): CI/CD integration guide
- [multi-agent.md](multi-agent.md): multi-agent orchestration guide
- [configuration.md](configuration.md): configuration schema and container reference
- [examples/README.md](examples/README.md): persona-based example configs and images
- [session-log.md](session-log.md): event types and log semantics
- [trust-model.md](trust-model.md): guarantees, limitations, and degraded modes
Carranca runs headless in CI pipelines without special flags. This guide
covers the patterns for using it in automated environments.
## Headless execution
`carranca run` auto-detects non-TTY environments and adjusts container
flags accordingly (omits `-t`). No `--non-interactive` flag is needed.
For CI use, configure the agent with the `stdin` adapter or pass the
prompt as part of the command:
```yaml
agents:
- name: codex
adapter: codex
command: "codex --quiet --approval-mode full-auto"
```
The agent receives its command from `.carranca.yml` and runs to
completion. When stdin is not a TTY, agents that support non-interactive
mode (like codex with `--quiet`) work without modification.
## Timeout
Use `--timeout <seconds>` to enforce a wall-clock budget:
```bash
carranca run --agent codex --timeout 600
```
When both `--timeout` and `policy.max_duration` in `.carranca.yml` are
set, the minimum wins. This lets the project config set a ceiling while
the CI job sets a tighter per-run limit.
## Exit codes
Pipeline branching should use these exit codes:
| Code | Meaning |
|------|---------|
| 0 | Agent succeeded, no policy violations |
| 1–125 | Agent exit code (pass-through) |
| 71 | Logger lost — audit trail interrupted (EX_OSERR) |
| 124 | Session timed out (max_duration exceeded) |
Exit code 71 means the audit trail was interrupted. The agent may have
succeeded, but the session is not verifiable. Treat this as a failure
in compliance-sensitive pipelines.
Exit code 124 matches the `timeout(1)` convention. The session was
killed after the configured duration.
## Session logs as CI artifacts
After `carranca run`, export the session log as a signed archive:
```bash
# Parse repo ID from carranca status (first line: "Repo: <name> (<id>)")
REPO_ID=$(carranca status 2>/dev/null | head -1 | grep -oE '[a-f0-9]{12}')
# Find the latest session log by modification time
SESSION_ID=$(basename "$(ls -t ~/.local/state/carranca/sessions/"$REPO_ID"/*.jsonl 2>/dev/null | head -1)" .jsonl)
# Export as .tar + .tar.sig
carranca log --export --session "$SESSION_ID"
```
The export produces two files next to the session log:
- `<session-id>.tar` — contains the JSONL log, HMAC key, and checksums
- `<session-id>.tar.sig` — HMAC-SHA256 signature of the archive
## Session ID discovery
The session ID is a 16-character hex string generated at session start.
To discover it after a run, parse the repo ID from `carranca status` and
list session logs by modification time:
```bash
REPO_ID=$(carranca status 2>/dev/null | head -1 | grep -oE '[a-f0-9]{12}')
ls -t ~/.local/state/carranca/sessions/"$REPO_ID"/*.jsonl | head -1
# Or inspect recent sessions interactively
carranca status
```
## Example: GitHub Actions workflow
```yaml
name: Agent task
on: [workflow_dispatch]
jobs:
agent-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install carranca
run: |
git clone https://github.com/pboueke/carranca ~/.local/share/carranca
ln -s ~/.local/share/carranca/cli/carranca ~/.local/bin/carranca
- name: Run agent
run: carranca run --agent codex --timeout 600
- name: Export session log
if: always()
run: |
REPO_ID=$(carranca status 2>/dev/null | head -1 | grep -oE '[a-f0-9]{12}' || true)
SESSION=$(basename "$(ls -t ~/.local/state/carranca/sessions/"$REPO_ID"/*.jsonl 2>/dev/null | head -1)" .jsonl || true)
if [ -n "$SESSION" ]; then
carranca log --export --session "$SESSION"
fi
- name: Upload session archive
if: always()
uses: actions/upload-artifact@v4
with:
name: carranca-session
path: ~/.local/state/carranca/sessions/**/*.tar*
if-no-files-found: ignore
```
## Real-world example: automated PR review
Carranca's own repository uses a PR review workflow that demonstrates
CI automation end-to-end. When a PR is opened against `main`, a
GitHub Actions job:
1. Generates a diff and review prompt from the PR metadata
2. Runs `carranca run --agent reviewer --timeout 600` with a `stdin`
adapter agent that invokes an AI model inside the sandbox
3. Posts the structured review back as a PR comment
The workflow restricts execution to PRs from the repository owner to
protect the API key secret. Only the AI provider key enters the
container via `environment.passthrough`; the GitHub token stays on the
runner and is used only for posting the comment.
Key patterns from this setup:
- **Agent script as a workspace file**: the workflow generates
`_review.sh` at runtime and the agent command is simply
`bash /workspace/_review.sh`, avoiding YAML escaping issues with
complex inline commands
- **Engine override**: the project config specifies `runtime.engine:
podman` for local use, but the CI job sets
`CARRANCA_CONTAINER_RUNTIME=docker` to match the runner
- **No cache volume**: `volumes.cache: false` is appropriate for
ephemeral CI runs where the agent home directory does not need to
persist
See `.github/workflows/pr-review.yml` and the
[ci-reviewer example](examples/ci-reviewer/) for the full
configuration.
## Recommended policy for CI
For unattended runs, set explicit limits in `.carranca.yml`:
```yaml
policy:
max_duration: 1800 # 30 minute ceiling
resource_limits:
memory: 4g
cpus: "2.0"
pids: 256
docs_before_code: warn # or enforce
tests_before_impl: warn # or enforce
runtime:
network:
default: deny
allow:
- "registry.npmjs.org:443"
- "*.anthropic.com:443"
```
This ensures the agent cannot run indefinitely, consume unbounded
resources, or make unexpected network connections.
## Verification
To verify a session log after a CI run (e.g., in a follow-up audit
step):
```bash
carranca log --verify --session "$SESSION_ID"
```
This replays the HMAC chain and checksums, reporting any tampering.
## Overview
Each carranca session produces a single JSONL file at:
```
~/.local/state/carranca/sessions/<repo-id>/<session-id>.jsonl
```
One JSON object per line. Events are ordered by `seq` (monotonic integer).
Carranca writes these logs for `run` sessions. The separate `config` workflow
also writes audit events, but those live under `~/.local/state/carranca/config/`
instead of the session log directory.
## Event types
### `session_event`
Session lifecycle events produced by carranca itself.
```json
{"type":"session_event","source":"carranca","event":"start","ts":"2026-03-22T09:45:00Z","session_id":"abc12345","repo_id":"a1b2c3d4e5f6","repo_name":"my-app","repo_path":"/home/user/my-app","agent":"codex","adapter":"codex","engine":"podman","seq":1}
{"type":"session_event","source":"carranca","event":"degraded","ts":"2026-03-22T09:45:00Z","session_id":"abc12345","reason":"append_only_unavailable","seq":2}
{"type":"session_event","source":"shell-wrapper","event":"agent_start","ts":"2026-03-22T09:45:02Z","session_id":"abc12345","seq":3}
{"type":"session_event","source":"shell-wrapper","event":"agent_stop","ts":"2026-03-22T09:57:34Z","session_id":"abc12345","exit_code":0,"seq":10}
{"type":"session_event","source":"carranca","event":"logger_stop","ts":"2026-03-22T09:57:35Z","session_id":"abc12345","seq":11}
```
Events currently emitted here: `start`, `degraded`, `agent_start`,
`agent_stop`, `logger_stop`, `observer_start`, `observer_stop`
Typical lifecycle patterns:
- Normal completion: `start` → `agent_start` → `agent_stop` → `logger_stop`
- Interrupted run (`Ctrl+C`) or `carranca kill`: Carranca still tries to stop the
agent first and then the logger, so a clean session usually still ends with
`logger_stop`
- Crash or host failure: the tail of the lifecycle may be missing; absence of
`logger_stop` should be treated as incomplete shutdown evidence, not proof that
no cleanup was attempted
### `shell_command`
Commands executed through the shell wrapper.
```json
{"type":"shell_command","source":"shell-wrapper","ts":"2026-03-22T09:45:02Z","session_id":"abc12345","command":"npm test","exit_code":0,"duration_ms":3420,"cwd":"/workspace/src","seq":4}
```
Today this records the top-level configured agent command as seen by the
wrapper. It does not trace every subprocess or internal tool call the agent may
spawn after startup.
**Limitation:** Agent-native operations such as direct file edits via tool APIs
still bypass shell-wrapper command capture.
### `file_event`
File mutations detected by `inotifywait` (Linux, best-effort) or `fswatch`
(fallback for non-Alpine images where inotifywait is unavailable).
The `source` field identifies which watcher produced the event.
```json
{"type":"file_event","source":"inotifywait","ts":"2026-03-22T09:45:03Z","event":"MODIFY","path":"/workspace/src/index.ts","session_id":"abc12345","seq":5}
{"type":"file_event","source":"inotifywait","ts":"2026-03-22T09:45:03Z","event":"CREATE","path":"/workspace/.env","session_id":"abc12345","seq":6}
```
Events: `CREATE`, `MODIFY`, `DELETE`
When a file event matches a `watched_paths` pattern from `.carranca.yml`, the
event includes `"watched":true`:
```json
{"type":"file_event","source":"inotifywait","ts":"2026-03-22T09:45:03Z","event":"CREATE","path":"/workspace/.env","session_id":"abc12345","watched":true,"seq":6}
```
**Limitation:** No attribution. The file watcher sees that a path changed, but
it cannot identify which process caused it. Reads are not captured.
## HMAC chain
Each event includes an `hmac` field that chains to the previous event,
making tampering detectable. The HMAC is computed over:
```
{previous_hmac}|{seq}|{ts}|{event_payload}
```
- `previous_hmac`: Empty string ("0") for the first event, otherwise the previous event's HMAC
- `seq`: Monotonic sequence number
- `ts`: Event timestamp
- `event_payload`: The full JSON object including the `seq` field
The per-session HMAC key is stored at:
```
~/.local/state/carranca/sessions/<repo-id>/<session-id>.hmac-key
```
This file is only accessible from the logger container, not the agent container.
Example event with HMAC:
```json
{"type":"session_event","source":"carranca","event":"start","ts":"2026-03-22T09:45:00Z","session_id":"abc12345","repo_id":"a1b2c3d4e5f6","repo_name":"my-app","repo_path":"/home/user/my-app","agent":"codex","adapter":"codex","engine":"podman","seq":1,"hmac":"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6"}
```
Verify a session's integrity from the local Carranca state directory:
```bash
carranca log --verify --session <id>
```
Any modification to the JSONL file after it was written breaks the HMAC chain.
## Checksum file
A parallel SHA-256 checksum file is written alongside each session log:
```
~/.local/state/carranca/sessions/<repo-id>/<session-id>.checksums
```
Each line contains the SHA-256 hash of the corresponding line in the
`.jsonl` file. This provides tamper detection even when kernel-level
append-only (`chattr +a`) is unavailable (e.g., rootless Podman,
macOS).
The checksum file is written by the logger container only and lives on the
same `/state` volume that the agent container cannot access.
Checksum verification runs automatically during `carranca log --verify`. If the
checksum file is missing (sessions predating this feature), verification
proceeds using the HMAC chain only.
## Log export and archival
`carranca log --export` produces a self-contained signed archive for external
storage, compliance review, or incident postmortem:
```bash
carranca log --export --session abc12345
```
This creates two files alongside the session log:
```
~/.local/state/carranca/sessions/<repo-id>/<session-id>.tar
~/.local/state/carranca/sessions/<repo-id>/<session-id>.tar.sig
```
The tar archive bundles the `.jsonl`, `.hmac-key`, and `.checksums` files. The
`.sig` file contains an HMAC-SHA256 signature of the tar computed with the
session key. If the HMAC key is missing (older sessions), the signature
is an unsigned SHA-256 digest.
To verify an exported archive independently:
```bash
# Verify the archive signature
KEY="$(cat <session-id>/<session-id>.hmac-key)"
openssl dgst -sha256 -macopt "hexkey:$KEY" -hex <session-id>.tar | awk '{print $NF}'
cat <session-id>.tar.sig
```
For full `carranca log --verify` replay, restore the extracted `.jsonl`,
`.hmac-key`, and `.checksums` files into a Carranca session state directory.
### Archive signature limitations
The archive signature proves consistency with the session's HMAC chain, not
independent authenticity. The HMAC key lives alongside the log in `/state/`, so
anyone with `/state/` access can produce valid signatures. For true
non-repudiation, asymmetric signing (ed25519) with an external key would be
needed.
### `heartbeat`
Periodic liveness check from the shell wrapper (every 30s).
```json
{"type":"heartbeat","source":"shell-wrapper","ts":"2026-03-22T09:45:32Z","session_id":"abc12345","seq":7}
```
### `invalid_event`
Malformed data received on the FIFO.
```json
{"type":"invalid_event","source":"fifo","ts":"2026-03-22T09:45:05Z","session_id":"abc12345","raw":"not valid json","seq":8}
```
### `execve_event`
Process execution captured by strace. When
`observability.independent_observer: true`, the observer sidecar always runs
strace from outside the agent's PID namespace (source: `observer`) regardless
of the `execve_tracing` setting. When `independent_observer` is not enabled,
requires `observability.execve_tracing: true` for the logger to run strace
internally (source: `strace`).
```json
{"type":"execve_event","source":"strace","ts":"2026-03-22T09:45:03Z","session_id":"abc12345","pid":42,"binary":"/usr/bin/npm","argv":"[\"npm\", \"test\"]","seq":9}
{"type":"execve_event","source":"observer","ts":"2026-03-22T09:45:03Z","session_id":"abc12345","pid":42,"binary":"/usr/bin/npm","argv":"[\"npm\", \"test\"]","seq":9}
```
### `network_event`
Outbound network connection detected by polling `/proc/net/tcp`.
Requires `observability.network_logging: true`. When the independent observer
is active, the observer polls from outside the agent's namespace (source:
`observer`). Otherwise, the logger polls via PID namespace sharing (source:
`carranca`). Only active when `runtime.network` is enabled.
```json
{"type":"network_event","source":"carranca","ts":"2026-03-22T09:45:03Z","session_id":"abc12345","dest_ip":"104.18.12.33","dest_port":443,"protocol":"tcp","state":"ESTABLISHED","seq":10}
```
### `resource_event`
Periodic container resource sample from cgroup stats.
Configurable via `observability.resource_interval` (default: 10 seconds).
```json
{"type":"resource_event","source":"carranca","ts":"2026-03-22T09:45:10Z","session_id":"abc12345","cpu_usage_us":1234567,"memory_bytes":52428800,"pids":12,"seq":11}
```
### `file_access_event`
File read detected by fanotify on `watched_paths`.
Requires `observability.secret_monitoring: true` and `CAP_SYS_ADMIN` on the
logger container.
```json
{"type":"file_access_event","source":"fanotify","ts":"2026-03-22T09:45:03Z","session_id":"abc12345","path":"/workspace/.env","pid":42,"watched":true,"seq":12}
```
### `policy_event`
Policy enforcement events produced by Carranca's enforcement mechanisms. These
record when policies are configured, enforced, violated, or degraded.
```json
{"type":"policy_event","source":"carranca","ts":"2026-03-22T09:45:00Z","session_id":"abc12345","policy":"resource_limits","action":"oom_kill","detail":"OOM kill detected (limit: 2g)","seq":13}
{"type":"policy_event","source":"carranca","ts":"2026-03-22T09:45:00Z","session_id":"abc12345","policy":"max_duration","action":"timeout","detail":"session killed after 3600s","seq":14}
{"type":"policy_event","source":"carranca","ts":"2026-03-22T09:45:00Z","session_id":"abc12345","policy":"filesystem","action":"enforced","detail":"read-only: .env,secrets/","seq":15}
{"type":"policy_event","source":"carranca","ts":"2026-03-22T09:45:00Z","session_id":"abc12345","policy":"filesystem","action":"degraded","detail":"glob patterns not enforced: *.key","seq":16}
{"type":"policy_event","source":"pre-commit-hook","ts":"2026-03-22T09:45:05Z","session_id":"abc12345","policy":"docs_before_code","action":"blocked","detail":"commit modifies src/app.js without documentation changes","seq":17}
{"type":"policy_event","source":"pre-commit-hook","ts":"2026-03-22T09:45:05Z","session_id":"abc12345","policy":"tests_before_impl","action":"warn","detail":"commit modifies lib/parser.sh without test changes","seq":18}
{"type":"policy_event","source":"carranca","ts":"2026-03-22T09:45:00Z","session_id":"abc12345","policy":"network","action":"configured","detail":"mode:filtered rules:1.2.3.4:443,5.6.7.8:443","seq":19}
```
| Field | Values |
|-------|--------|
| `policy` | `resource_limits`, `max_duration`, `filesystem`, `docs_before_code`, `tests_before_impl`, `network` |
| `action` | `enforced`, `blocked`, `warn`, `timeout`, `oom_kill`, `configured`, `degraded` |
| `source` | `carranca` (logger-side), `pre-commit-hook` (git hook), `network-setup` (iptables) |
### `integrity_event`
FIFO forgery detection events produced by the logger when incoming events
fail structural or temporal validation. These events are always emitted
(zero-config) and do not suppress the original event — both are logged.
```json
{"type":"integrity_event","source":"carranca","ts":"2026-03-22T09:45:05Z","session_id":"abc12345","reason":"missing_required_fields","raw_source":"unknown","seq":20}
{"type":"integrity_event","source":"carranca","ts":"2026-03-22T09:45:05Z","session_id":"abc12345","reason":"timestamp_future","raw_source":"shell-wrapper","seq":21}
{"type":"integrity_event","source":"carranca","ts":"2026-03-22T09:45:05Z","session_id":"abc12345","reason":"seq_injection_attempt","raw_source":"shell-wrapper","seq":22}
{"type":"integrity_event","source":"carranca","ts":"2026-03-22T09:45:05Z","session_id":"abc12345","reason":"source_impersonation","raw_source":"strace","seq":23}
```
| Reason | Trigger |
|--------|---------|
| `missing_required_fields` | FIFO event missing `type`, `source`, `ts`, or `session_id` |
| `timestamp_before_session` | Event `ts` is before the session start time |
| `timestamp_future` | Event `ts` is more than 5 seconds in the future |
| `timestamp_regression` | Event `ts` regresses more than 60 seconds from the previous FIFO event |
| `seq_injection_attempt` | FIFO event contains `seq` or `hmac` fields (logger is sole authority) |
| `source_impersonation` | FIFO event claims a `source` that only writes directly to the log (`strace`, `inotifywait`, `carranca`, etc.) |
| `observer_token_invalid` | FIFO event claims `source:"observer"` but does not carry a valid authentication token |
| `shell_command_without_execve` | Shell command reported by agent has no corresponding execve event from the observer within ±3s (best-effort heuristic; review recommended) |
| `unmatched_execve_activity` | Execve event observed by the observer has no corresponding shell command from the agent within ±3s (best-effort heuristic; review recommended) |
### Session timeline
`carranca log --timeline` renders an ASCII timeline of all session events:
```bash
carranca log --timeline --session abc12345
```
Each event type maps to a glyph: `>>` lifecycle, `$` command, `F+/F~/F-`
file mutation, `X` execve, `N` network, `R` resource, `A` file access,
`P` policy, `!` integrity.
## Event provenance
The `source` field identifies the component that produced each event. This
enables consumers to distinguish ground-truth observations from agent-reported
data.
| Source value | Origin | Trust level |
|-------------|--------|-------------|
| `carranca` | Logger container (session lifecycle, resource sampling, network monitoring) | Ground truth — produced by carranca itself |
| `inotifywait` | Linux file watcher | Ground truth — kernel-level observation |
| `fswatch` | macOS file watcher | Ground truth — OS-level observation |
| `strace` | Process execution tracer | Ground truth — kernel-level observation via ptrace |
| `fanotify` | File access monitor | Ground truth — kernel-level observation via fanotify |
| `observer` | Independent sidecar container | Ground truth — runs outside agent namespace, agent cannot interfere |
| `shell-wrapper` | Agent container shell wrapper | Agent-reported — the agent can forge or suppress these |
| `pre-commit-hook` | Carranca-injected git pre-commit hook | Agent-side — runs in agent container but managed by carranca |
| `network-setup` | Network policy iptables setup | Agent-side — runs before agent command, managed by carranca |
| `fifo` | Malformed data on the FIFO | Untrusted — raw data that failed validation |
## Schema fields
| Field | Type | Description |
|-------|------|-------------|
| `seq` | int | Monotonic sequence number — enables ordering and gap detection |
| `ts` | string | ISO 8601 UTC timestamp |
| `type` | string | Event type (see above) |
| `source` | string | Component that produced the event |
| `session_id` | string | 16-char hex session identifier |
| `repo_id` | string | 12-char hex repo identifier |
| `hmac` | string | SHA-256 HMAC of the event chain (see HMAC chain section) |
## Querying
```bash
# All shell commands
jq 'select(.type=="shell_command")' session.jsonl
# Failed commands
jq 'select(.type=="shell_command" and .exit_code != 0)' session.jsonl
# File mutations
jq 'select(.type=="file_event")' session.jsonl
# Session timeline
jq '{seq, type, event, command, path}' session.jsonl
# Count commands
jq -s '[.[] | select(.type=="shell_command")] | length' session.jsonl
# Verify HMAC chain and checksum integrity
carranca log --verify --session abc12345
```
## `carranca log`
`carranca log` reads one session JSONL file and prints a compact summary for
developers:
- session and repo identifiers
- start/end timestamps
- unique paths touched
- file-event totals split by create, modify, and delete
- top touched paths ranked by event count
- command totals, failures, and ordered shell-wrapper command list
Examples:
```bash
# Latest session for the current repo
carranca log
# Exact session id for the current repo
carranca log --session abc12345
```
Important nuance: file counts shown by `carranca log` distinguish between
`Unique paths touched` and raw `File events`. Repeated writes to the same path
increase the event count without increasing the unique-path count.
If a session shows file activity but `Commands run: 0`, that usually means the
agent changed files through native tool APIs or edits that bypassed shell-wrapper
command capture.
## `carranca config` audit log
The configurator has its own audit trail:
```text
~/.local/state/carranca/config/<repo-id>/history.jsonl
```
Current event types include:
- `no_changes`
- `proposal_rejected`
- `confirmation_bypassed`
- `applied`
Those records are not mixed into per-session `run` logs.
## `carranca status` and `carranca kill`
The JSONL log is the durable record for a session, but active-state inspection is
container-based:
- `carranca status` marks a session as active when its logger, agent, or observer container
still exists
- `carranca kill --session <id>` stops that exact session after confirmation
- `carranca kill` stops all active Carranca sessions globally after confirmation
This means a session can have a log file and still be marked active while the
containers are running, and older sessions remain queryable through `log` even
after teardown.
## Design assumption Carranca is designed to handle both **cooperative agents** (buggy or careless) and **untrusted agents** (potentially adversarial). The default configuration applies adversarial hardening: all capabilities dropped, read-only root filesystem, seccomp filtering, and FIFO forgery detection. With `observability.independent_observer: true`, the runtime provides independent kernel-level ground truth that the agent cannot interfere with, enabling cross-referencing to detect unreported or forged activity. ## What carranca provides | Property | Strength | Mechanism | |----------|----------|-----------| | Host isolation | Hard boundary | Container runtime boundary through Podman or Docker; the agent only sees mounted paths | | Session logging | Transparency tool | Shell wrapper writes events to FIFO, logger writes JSONL | | Event provenance | Transparency tool | Each event tagged with origin source for trust-level analysis | | Fail-closed logging | Hard boundary | Broken FIFO pipe kills the shell wrapper → agent stops | | Append-only log | Best-effort | `chattr +a` when `CAP_LINUX_IMMUTABLE` is available | | HMAC event chain | Verified evidence | Per-session HMAC key signs each event; chain detects tampering | | Checksum hardening | Verified evidence | Parallel SHA-256 checksum file for tamper detection without `chattr +a` | | Log export | Archival | Self-contained signed tar archive for compliance and forensics | | execve tracing | Ground truth | strace captures all process execution in agent PID namespace | | Independent observer | Ground truth | Execve/network monitoring in separate sidecar outside agent's namespaces; observer events authenticated via shared token on `/state/` (agent cannot access). Cross-referencing at session end is a best-effort heuristic (±3s window, greedy 1:1 match) that flags anomalies for human review | | Network logging | Ground truth | `/proc/net/tcp` polling captures outbound connections | | Secret read monitoring | Ground truth | fanotify captures file reads on watched paths | | Resource tracking | Transparency tool | cgroup v2 stats sample CPU, memory, and PID count | | Resource limits | Hard boundary | Container runtime enforces `--memory`, `--cpus`, `--pids-limit` via cgroup limits | | Time-boxed sessions | Hard boundary | Logger removes FIFO after `policy.max_duration` seconds, triggering agent fail-closed exit | | Filesystem access control | Hard boundary | Bind-mount `:ro` overlays prevent writes to `watched_paths` entries | | Policy hooks | Enforcement tool | Git `core.hooksPath` injects pre-commit hooks; `enforce` mode blocks policy-violating commits | | Network policies | Hard boundary | iptables rules restrict outbound traffic to allow-listed destinations (rootful mode only) | | Capability drop | Hard boundary | `--cap-drop ALL` removes all Linux capabilities; `runtime.cap_add` is a strict allowlist | | Read-only root FS | Hard boundary | `--read-only` prevents agent from modifying its own runtime environment; explicit tmpfs for writable paths | | Seccomp filtering | Hard boundary | Default seccomp profile blocks dangerous syscalls (ptrace, mount, unshare, module loading, etc.) | | AppArmor confinement | Opt-in hardening | Reference AppArmor profile restricts file access, mounts, and ptrace; user must load and configure | ## Current limitations and non-goals | Limitation | Status | Notes | |------------|--------|-------| | Formal proof of agent honesty | Not provided | FIFO validation, token-authenticated observer events, and cross-referencing raise evidence quality, but the end-of-session matching remains a best-effort heuristic for human review | | Remote attestation of the host, kernel, or container runtime | Not provided | Carranca trusts the operator-controlled machine and runtime; it does not produce third-party-verifiable attestation today | | Complete semantic capture of every file change | Not provided | Carranca logs shell activity, execve events, file mutations, and watched-path reads, but it does not record a syscall-level proof of every byte written | | Guaranteed fine-grained egress filtering in every runtime mode | Partially provided | Filtered allow-lists use iptables and require a mode that can apply those rules; rootless Podman falls back to `--network=none` instead of filtered allow-list enforcement | | Strong cross-platform parity | Not provided | Linux has the most complete observability and enforcement model; macOS and Windows remain experimental | | Protection from a malicious host operator or host root | Not provided | A host user with sufficient privilege can alter the runtime environment, inspect mounts, or interfere with containers outside Carranca's boundary | | Prevention of bad code or bad intent | Not provided | Carranca constrains and records agent behavior; it does not decide whether the resulting code is safe or correct | ## Threat table | Threat | Status | Notes | |--------|--------|-------| | Agent edits/deletes session log | Mitigated | Agent container has no mount to log directory | | Agent forges shell events via FIFO | Partially mitigated | Structural/temporal validation detects malformed forgeries; observer events are token-authenticated; cross-referencing flags timing anomalies but uses a ±3s heuristic | | Agent suppresses events (kills FIFO) | Mitigated | Shell wrapper exits on broken pipe → agent stops | | Agent bypasses shell wrapper | Partially mitigated | Independent observer captures execve calls from outside agent namespace; cross-referencing flags unreported activity as a heuristic for review. Observer must attach to the container init process to trace all descendants | | Agent reads secrets from repo | Mitigated | fanotify captures file reads on `watched_paths` when `observability.secret_monitoring: true` | | Agent exfiltrates over network | Partially mitigated | Network logging records outbound connections; set `runtime.network: false` to disable entirely | | Agent connects via IPv6 to bypass allow-list | Partially mitigated | iptables filters IPv4 only; IPv6 destinations bypass the allow-list. Degradation warning and policy event logged. Mitigate with `runtime.network: false` for full isolation | | Agent exfiltrates via DNS tunneling | Known limitation | DNS queries to configured resolvers are unrestricted. Data can be encoded in DNS subdomains. Use `runtime.network: false` or a DNS filtering proxy for high-security environments | | Agent writes malicious code | Allowed by design | Review via session log + git diff | | Malicious `.carranca.yml` (repo compromise) | Operator-scoped risk | `.carranca.yml` is operator-authored trusted input; `eval` of `AGENT_COMMAND` is by design to support shell syntax. If an attacker controls `.carranca.yml` they control the agent command — same as controlling any shell script in the repo. Config is hidden from the agent at runtime via `/dev/null` bind mount | | Agent reads runtime policy config | Mitigated | `.carranca.yml` and `.carranca/` are hidden from the agent container via bind-mount overlays; `carranca config` redacts policy-sensitive fields before exposing config to the config agent | | User tampers with log after session | Partially mitigated | HMAC chain + checksum file detect post-session tampering; `chattr +a` when available. Without `chattr +a` (rootless Podman), truncation and re-append with a valid HMAC chain is possible. Run `carranca log --verify` after sessions; consider external log shipping for high-assurance environments | ## Failure behavior | Scenario | Behavior | |----------|----------| | Shell logging unavailable (FIFO not created) | Fail closed — refuse to start agent | | File event logging unavailable (no file watcher) | Degraded mode — shell logging continues | | Logger dies mid-session | Fail closed — broken pipe kills agent | | `chattr +a` unavailable | Degraded mode — log still written, not append-only | | strace unavailable or `SYS_PTRACE` denied | Degraded mode — session proceeds without execve tracing | | cgroup mount unreadable | Degraded mode — session proceeds without resource tracking | | fanotify unavailable or `SYS_ADMIN` denied | Degraded mode — session proceeds without secret read monitoring | | `/proc/net/tcp` unreadable (no PID ns sharing) | Degraded mode — session proceeds without network logging | ## Summary Carranca is an isolation, audit, policy-enforcement, and adversarial-hardening runtime. It makes sessions reviewable with cryptographic tamper detection, kernel-observed telemetry, and technical controls such as resource limits, time bounds, read-only root filesystem, capability drop, seccomp filtering, and network restrictions. FIFO forgery detection validates event structure and timing. The independent observer sidecar provides kernel-level ground truth outside the agent's namespaces, with observer events authenticated via a shared token on `/state/` that the agent cannot access. Cross-referencing at session end is a best-effort heuristic (±3s timestamp window, greedy 1:1 matching) that flags anomalies for human review — it is not a proof of forgery. Post-session log tampering is detectable via the HMAC chain and parallel checksum file. The current value proposition is not blind trust, but observable execution with enforceable guardrails, adversarial hardening, and verifiable evidence.
## 0.17.2
- fix: **HMAC/checksum chain broken across subshells** — `PREV_HMAC` and `_PREV_CHECKSUM_HASH` were shell variables (process-local), so background file watchers (`inotifywait`, `fswatch`, `fanotify`) forked with stale chain state; replaced with file-backed state read/written inside the existing `flock` block, matching how the seq counter already works
- test: add `test_log_verify.sh` — 13 tests covering write→verify roundtrip: sequential writes, cross-subshell writes, concurrent subshell writes, tamper detection, reorder detection, and deletion detection
## 0.17.1
- docs: fix stale roadmap — mark Phase 6 complete, update descriptions to match implementation (no `--non-interactive` flag, correct `--pretty` semantics, iptables not nftables)
- docs: fix ci.md placeholder URL (`yourorg/carranca` → `pboueke/carranca`) and simplify session ID discovery scripts
- docs: update configuration.md Containerfile example to match actual template (`lib/json.sh`, `iptables`, `/home/carranca`)
- docs: add commented-out `orchestration` and `environment` stubs to full `.carranca.yml` example in configuration.md
- docs: add `environment` sections to all persona examples demonstrating `passthrough`, `env_file`, and `vars` mechanisms
- docs: add PATH persistence note to README quick start; note local-only nature of technical reference link
- docs: update CONTRIBUTING.md to remove stale Docker-only workaround notes
- chore: switch changelog headers from `## [X.Y.Z] - date` to `## X.Y.Z`; update all version parsers (Makefile, pre-commit hook, badge updater, doc page builder)
- chore: expand Makefile `SHELL_SRC` and `SHELL_RUNTIME` to cover all shell scripts (74 files linted, up from ~40)
- chore: add missing agent Containerfile templates to `CONTAINERFILES` lint list
- chore: make `build` and `clean` Makefile targets auto-detect podman/docker instead of hardcoding docker
- chore: fix `test-all` help text to say "Docker or Podman"
## 0.17.0
- feat: **operator-provided environment variables** — new `environment` config section passes env vars from the host into agent containers via three mechanisms: `passthrough` (forward named host vars), `env_file` (load from a `.env` file), and `vars` (define directly in config); priority: vars > env_file > passthrough
- feat: **env_file parser** — standard `.env` format with `#` comments, `export` prefix, single/double quoted values, blank line handling; invalid variable names are rejected with warnings
- feat: **env validation at startup** — `carranca_env_validate` checks passthrough names and env_file existence during config validation; bad names fail fast, missing files abort before container launch
- docs: `environment.passthrough`, `environment.env_file`, and `environment.vars` fields added to `doc/configuration.md` with examples
- test: 918 tests, 44 suites, 0 failures, 100% function coverage (147/147)
## 0.16.1
- fix: **config sandbox hardened** — `carranca config` now applies `--cap-drop ALL`, `--read-only` root FS, seccomp profile, and AppArmor profile, matching the `carranca run` sandbox
- fix: **IPv6 network policy safety** — `run.sh` filters `getent ahosts` to IPv4 only with degradation warning; `network-setup.sh` has defense-in-depth check rejecting IPv6 entries; prevents ambiguous `IP:PORT` serialization and unenforced egress via IPv6
- fix: **allowlist-based config redaction** — replaced brittle `grep -vE` denylist with awk allowlist; only `agents`, `runtime`, and `volumes` sections pass through; `runtime.network` allow-list details stripped; unknown future sections denied by default
- test: 877 tests, 43 suites, 0 failures, 100% function coverage (142/142)
## 0.16.0
### Phase 6 — Ecosystem and integration
- feat: **`carranca diff`** — new command comparing two sessions across duration, agent, commands, files, resources, network, and policy; compact tab-separated default output, `--pretty` for human-readable format; supports cross-repo comparison via `--repo-a`/`--repo-b`
- feat: **`--timeout <seconds>`** on `carranca run` — CLI convenience for `policy.max_duration`; when both are set the minimum wins
- feat: **exit code 124 for timeouts** — sessions killed by `max_duration` now return 124 (matching `timeout(1)` convention) instead of the shell-wrapper's generic exit code; exit code 71 (logger loss) takes priority
- feat: **multi-agent orchestration** — run multiple agents in a single session with `orchestration.mode: pipeline|parallel`; each agent gets its own container, FIFO, logger, and security boundary
- feat: **workspace isolation** — `orchestration.workspace: isolated` gives each agent a `cp -a` copy of the workspace; pipeline mode supports `merge: carry` (next agent sees previous changes) or `merge: discard`
- feat: **orchestrator logging** — multi-agent sessions produce `*.orchestrator.jsonl` with per-agent start/stop events and overall session result; `carranca log` and `carranca status` detect and summarize orchestrated sessions
- refactor: **session lifecycle extraction** — core container operations (build, logger start, FIFO wait, observer start, agent run, post-agent checks) extracted from `cli/run.sh` into reusable functions in `cli/lib/lifecycle.sh`; `run.sh` reduced by ~200 lines
- refactor: **prefix-based session stop** — `carranca_session_stop`, `is_active`, and `exists` now enumerate containers by session prefix instead of fixed suffixes; supports both single-agent and multi-agent sessions
- docs: **CI/CD integration guide** (`doc/ci.md`) — headless execution, timeout, exit codes, session log artifacts, GitHub Actions example
- docs: **multi-agent guide** (`doc/multi-agent.md`) — config schema, pipeline vs parallel, workspace isolation, security model, examples
- docs: **roadmap updated** — central log aggregation (7.1) and plugin API (7.2) moved to new Phase 7
- docs: exit code table added to `doc/usage.md`; `diff` command documented; `orchestration.*` fields added to `doc/configuration.md`
- test: 725 tests, 34 suites, 0 failures
## 0.15.4
- fix: **config trust boundary** — agent container can no longer read `.carranca.yml` or `.carranca/` during `carranca run`; `.carranca/` hidden via tmpfs overlay, `.carranca.yml` hidden via `/dev/null` bind mount; prevents agents from learning network rules, watched paths, hooks, and timer limits
- fix: **config agent policy redaction** — `carranca config` now strips policy-sensitive fields (watched_paths, policy.*, observability.*) from `.carranca.yml` before mounting into the config agent container; agent sees only agents, runtime, and volumes sections
- chore: remove unused `plan` skill — was copied to `.carranca/skills/carranca/plan/` on init but never referenced by any agent prompt; `confiskill` (actively used by `cli/config.sh`) is preserved
- chore: remove orphan `.carranca/skills/plan/SKILL.md` (duplicate outside managed subdirectory)
- test: new `test_config_runner.sh` — 10 tests covering all 5 driver branches, missing prompt file, special chars, empty prompt
- test: new `test_shell_wrapper.sh` — 19 tests covering fail_closed behavior, write_event FIFO paths, FIFO wait loop, event JSON structure, heartbeat format
- test: new `test_network_setup.sh` — 29 tests covering _log, _fail_closed, _emit_enforcement_failure behavior, and full script structure verification
- test: `test_run.sh` updated — verifies agent sees empty `.carranca/` and empty `.carranca.yml`
- fix: **glob degradation warning** — `carranca run` now prints a visible warning at session start when glob patterns in `watched_paths` cannot be enforced as read-only
- fix: **degradation summary** — consolidated warning before "Agent ready" when security features degrade (rootless Podman network fallback, glob enforcement gaps)
- fix: **eval trust documentation** — `shell-wrapper.sh` and `config-runner.sh` `eval` usage documented as intentional (operator-authored config, not agent-controlled); added to `doc/trust-model.md` threat table
- fix: **test_kill.sh flaky timing** — wait for both agent containers before testing kill; prevents race where second session isn't ready
- test: `test_config.sh` updated — verifies policy redaction (agent cannot see docs_before_code, tests_before_impl)
- test: 767 tests, 39 suites, 0 failures, 100% function coverage
## 0.15.3
- docs: move persona configuration examples out of `doc/configuration.md` into standalone directories under `doc/examples/`
- docs: add one example per persona with `.carranca.yml`, `.carranca/Containerfile`, and a persona-specific README
- docs: reference the new examples directory from README, objective.md, usage.md, configuration.md, and regenerate `doc/page/index.html`
## 0.15.2
- fix: **JSON injection** — RFC 8259 compliant `json_escape()` in new shared `runtime/lib/json.sh`; applied to all JSON producers: fanotify-watcher.c, strace-parser.sh, observer.sh, logger.sh, shell-wrapper.sh
- fix: **fanotify-watcher.c** — add `json_escape_string()` C function for path escaping; harden readlink bounds check; skip events on buffer overflow
- fix: **network-setup.sh fail-closed** — exit 1 when iptables is unavailable or fails instead of silently running with full network access; opt-in `CARRANCA_NETWORK_ALLOW_DEGRADED` preserves old behavior
- fix: **DNS egress restriction** — filtered network mode now pins DNS (port 53) to container resolver IPs from `/etc/resolv.conf` instead of allowing any destination
- fix: **config parser fail-closed** — abort when security-critical nested keys (`runtime.network.default`, `policy.filesystem.*`, `policy.resource_limits.*`) are present but yq is unavailable
- fix: **extra_flags validation** — allowlist for `runtime.extra_flags` and `runtime.logger_extra_flags`; blocks `--privileged`, `--cap-add`, `--security-opt`, etc.; override with `--trust-repo-flags`
- fix: **config value validation** — `runtime.network`, `policy.max_duration`, `runtime.cap_add`, `runtime.seccomp_profile`, `runtime.apparmor_profile` now validated at startup
- fix: **symlink resolution in watched paths** — `realpath` resolves symlinks before bind-mount setup; paths resolving outside workspace are rejected
- fix: **seccomp denylist expansion** — 28 additional blocked syscalls including bpf, io_uring, userfaultfd, keyctl, perf_event_open, kexec, process_vm_readv/writev
- fix: **AppArmor profile tightened** — removed blanket `/** r` read; granular read allowlist for runtime dependencies; added deny rules for /etc/shadow, /root, /proc/kcore, /sys/firmware
- fix: **fail-closed logging** — FIFO timeout and mid-session logger loss now report "fail closed" reason in output
- fix: **logger.sh JSON guards** — write_log and _handle_file_event verify line ends with `}` before string surgery; inotifywait switched from JSON --format to TSV post-processing for safe path escaping
- fix: **checksum file protection** — `chattr +a` attempted on checksum file; checksum chain added (each entry hashes previous entry); backwards-compatible verification
- fix: **session ID entropy** — increased from 32 bits (8 hex chars) to 64 bits (16 hex chars)
- fix: **millisecond timestamps** — all event producers now use `%S.%3NZ`; FIFO validation tightened to ±2s future / 30s regression
- fix: **FIFO permissions** — changed from 0666 to 0620 (owner rw, group write)
- fix: **observer token validation** — hex-only check (`[0-9a-fA-F]+`) before interpolation in strace-parser.sh and observer.sh
- docs: archive signature limitations documented in code and session-log.md
- docs: session ID and timestamp format updates across architecture.md, session-log.md, page/index.html
- test: 652 tests, 33 suites, 0 failures, 93% function coverage
- test: new tests for json_escape (9), json_validate_line (4), config parser fail-closed (4), FIFO permissions, millisecond timestamps
## 0.15.1
- docs: rename `doc/vision.md` to `doc/objective.md` and rewrite it around current positioning, target users, non-goals, and comparison with other sandbox models
- docs: remove phased-development framing from all docs except `doc/roadmap.md`; present Carranca as a current-state runtime across README, trust model, usage, session log, and generated reference page
- docs: restructure the trust model so limitations and non-goals no longer list already-shipped capabilities under "does not provide"
- docs: regenerate `doc/page/index.html` from the updated markdown sources
- docs: add search/social metadata and JSON-LD to the generated docs page for better discoverability without hidden text
## 0.15.0
- feat: **capability drop** — `runtime.cap_drop_all: true` (default) adds `--cap-drop ALL` to the agent container; `runtime.cap_add` becomes a strict allowlist applied after the drop
- feat: **read-only root filesystem** — `runtime.read_only: true` (default) adds `--read-only` with explicit tmpfs mounts for `/tmp`, `/var/tmp`, `/run`; when cache is disabled, `/home/carranca` gets a tmpfs mount
- feat: **seccomp filtering** — default seccomp profile at `runtime/security/seccomp-agent.json` blocks dangerous syscalls (ptrace, mount, unshare, reboot, module loading, pivot_root, swapon/swapoff, sethostname/setdomainname, setns); configurable via `runtime.seccomp_profile`
- feat: **AppArmor reference profile** — `runtime/security/apparmor-agent.profile` shipped as opt-in reference; configurable via `runtime.apparmor_profile`
- feat: **FIFO forgery detection** — `_validate_fifo_event()` in logger validates every FIFO event: required fields, timestamp bounds (before session, future, regression), seq/hmac injection stripping, source impersonation detection; emits `integrity_event` entries
- feat: **independent observer sidecar** — `observability.independent_observer: true` launches a third container with `--pid=host` and `CAP_SYS_PTRACE` that runs execve tracing and network monitoring outside the agent's namespaces; observer events authenticated via shared token on `/state/` (inaccessible to agent)
- feat: **cross-referencing** — logger compares shell_command and execve_event timestamps at session end with greedy 1:1 matching (±3s window); flags anomalies as best-effort heuristics for human review
- feat: **shared strace parser** — `runtime/lib/strace-parser.sh` extracted from logger.sh; sourced by both logger (legacy path) and observer (independent path)
- feat: `integrity_event` JSONL event type with `reason` and `raw_source` fields; `!` glyph in `--timeline`
- feat: observer lifecycle events (`observer_start`, `observer_stop`) in session log
- feat: session cleanup updated to stop observer container between agent and logger teardown
- docs: roadmap updated to reflect completed hardening work
- docs: trust model updated — design assumption expanded for untrusted agents; threat table reflects honest scope of each mitigation
- docs: architecture updated with observer sidecar and agent hardening flags
- docs: session-log updated with `integrity_event` type, observer source provenance, and cross-reference reasons
- docs: configuration updated with `runtime.cap_drop_all`, `runtime.read_only`, `runtime.seccomp_profile`, `runtime.apparmor_profile`, `observability.independent_observer`
- test: 562 tests, 28 unit suites, 0 failures
- test: new suites — `test_cap_drop.sh`, `test_seccomp_profile.sh`, `test_fifo_validation.sh`, `test_observer.sh`, `test_strace_parser.sh`
- test: updated suites — `test_session.sh` (observer cleanup), `test_execve_parser.sh` (shared parser)
## 0.14.0
- feat: **resource limits** — `policy.resource_limits.{memory,cpus,pids}` passed as `--memory`, `--cpus`, `--pids-limit` container runtime flags; kernel enforces via cgroup limits
- feat: **OOM kill detection** — resource sampler polls cgroup `memory.events` for `oom_kill` counter increments; emits `policy_event` with `action: oom_kill`
- feat: **time-boxed sessions** — `policy.max_duration` (seconds) triggers FIFO removal after the wall-clock limit, which activates the agent's fail-closed watchdog and terminates the session
- feat: **filesystem access control** — `policy.filesystem.enforce_watched_paths: true` bind-mounts `watched_paths` directories and files as read-only overlays; glob patterns degrade gracefully (monitored but not enforced)
- feat: **technical policy hooks** — `policy.docs_before_code` and `policy.tests_before_impl` now accept `enforce` (block commit), `warn` (allow + log), or `off`; enforced via git `core.hooksPath` pointing to carranca-managed pre-commit hook injected into the agent container
- feat: **fine-grained network policies** — `runtime.network` supports an object form (`default: deny` + `allow:` list) alongside the existing boolean; implemented via iptables OUTPUT DROP + per-IP allow rules in `network-setup.sh`, a privilege-dropping entrypoint wrapper
- feat: **`policy_event` event type** — new JSONL event for all enforcement actions with `policy`, `action`, and `detail` fields; `P` glyph in `--timeline`; counted in session summary
- feat: **`network-setup.sh`** — agent entrypoint wrapper that runs as root, applies iptables rules, creates a matching user via `adduser`, then drops privileges with `su` before exec-ing shell-wrapper
- feat: rootless Podman graceful degradation for network policies (falls back to `--network=none`; clears policy env vars so logger does not emit false-positive enforcement events)
- feat: `policy.*` keys added to global config fallback alongside `runtime.*`, `volumes.*`, and `observability.*`
- docs: `policy_event` documented in session-log.md with field reference and provenance table entries for `pre-commit-hook` and `network-setup` sources
- docs: all policy fields documented in configuration.md; `runtime.network.default` restricted to `deny` only
- docs: trust model updated — "Technical policy enforcement" moved from "Not provided" to "Provided"
- docs: architecture.md updated with `network-setup.sh` conditional entrypoint
- docs: roadmap updated to reflect completed policy-enforcement work
- docs: move the changelog to `doc/CHANGELOG.md` and update contributor/versioning docs to use the new canonical path
- test: 504 tests, 23 unit suites, 100% function coverage
- test: new suites — `test_policy_resource_limits.sh`, `test_policy_timer.sh`, `test_policy_filesystem.sh`, `test_policy_hooks.sh`, `test_policy_network.sh`
- test: updated suites — `test_timeline.sh` (policy P glyph), `test_resource_sampler.sh` (policy_event counting in summary), `test_config.sh` (policy global fallback)
## 0.13.0
- feat: add `carranca log --timeline` for ASCII timeline rendering of session events
- feat: add `execve` tracing via strace — captures all process execution in agent PID namespace (`observability.execve_tracing`)
- feat: add network connection logging by polling `/proc/net/tcp` (`observability.network_logging`)
- feat: add secret read monitoring via fanotify C binary — detects file reads on `watched_paths` (`observability.secret_monitoring`)
- feat: add resource consumption tracking via cgroup v2 stats (`observability.resource_interval`)
- feat: add `observability.*` config namespace with global fallback support
- feat: PID namespace sharing (`--pid=container`) when execve tracing or network logging is enabled
- feat: multi-stage Containerfile.logger build for fanotify-watcher C binary
- docs: add observability event types to session-log.md (execve_event, network_event, resource_event, file_access_event)
- docs: update trust model with deep observability capabilities and updated threat table
- docs: add observability config keys to configuration.md
- docs: roadmap updated to reflect completed observability work
- test: add coverage for timeline rendering, strace parser, network monitor, resource sampler, and fanotify integration
- test: 392 tests, 17 suites, 0 failures
## 0.12.0
- feat: add HMAC-signed event chain — per-session key signs each JSONL event with chained HMAC-SHA256; `carranca log --verify` validates the chain
- feat: add parallel SHA-256 checksum file for tamper detection when `chattr +a` is unavailable (rootless Podman, macOS)
- feat: add event provenance tagging — `source` field on all events distinguishes ground-truth observations from agent-reported data
- feat: add `carranca log --export` to produce self-contained signed archive (tar + HMAC signature) for compliance, forensics, and external storage
- feat: add `opencode` as a supported starter agent for `carranca init`
- feat: accept `opencode` as an explicit adapter and infer it from `adapter: default` when the command starts with `opencode`
- feat: run config agents with the `opencode` adapter through the argument-based prompt path
- fix: `carranca log --verify` now resolves the session log before verification (previously used unset variable)
- fix: install OpenCode from the official release binary instead of the broken npm wrapper package
- docs: update trust model with verified audit evidence, HMAC chain, checksum hardening, and log export
- docs: add event provenance trust table to session-log.md
- docs: roadmap updated to reflect completed audit-trail work
- docs: document `opencode` starter and adapter support across README and configuration docs
- docs: add `doc/vision.md` and link it from the README documentation index
- test: add coverage for HMAC functions, checksum verification, export archive, and provenance tagging
- test: add init, config, and help coverage for `opencode` starter and adapter flows
- test: 294 tests, 12 suites, 100% function coverage
## 0.11.0
- feat: add `runtime.cap_add` config — list of Linux capabilities passed as `--cap-add` flags to the agent container
- feat: wire up `watched_paths` — file events matching watched patterns are tagged with `"watched":true` in session logs
- feat: add agent, adapter, and engine metadata to session start events and log/status summary
- feat: add `--files-only`, `--commands-only`, and `--top N` filters to `carranca log`
- feat: add fswatch fallback for cross-platform file event monitoring
- feat: add global config at `~/.config/carranca/config.yml` for user-wide runtime and volume defaults
- feat: use `yq` as primary YAML parser when available, with awk fallback and schema compatibility warnings
- chore: replace TODOS.md with doc/roadmap.md for phased feature planning
## 0.10.0
- fix: detect and warn on cache ownership mismatch when switching between Docker and rootless Podman
- docs: rewrite CONTRIBUTING.md, README, architecture, configuration, session-log, and trust-model docs for accuracy and Podman-era terminology
## 0.9.0
- feat: add Podman and OCI runtime support with auto-detection (prefers Podman, falls back to Docker)
- feat: add `runtime.engine` config (`auto`, `docker`, `podman`) and `CARRANCA_CONTAINER_RUNTIME` env override
- feat: rootless Podman support with `--userns keep-id` for both logger and agent containers
- feat: add FIFO watchdog in agent shell-wrapper to fail closed when the logger disappears mid-session
- feat: version badge from CHANGELOG and HTML badge format in README
- fix: logger FIFO read loop exited on first timeout due to `!` inverting `$?` — sessions now survive slow container startup
- fix: logger and agent share the same user namespace on rootless Podman so the shared FIFO volume is accessible
- fix: use portable `-t` flag for container stop (replaces `--timeout` which Podman rejects)
- fix: test cleanup falls back to direct `rm` when `--cap-add LINUX_IMMUTABLE` is unavailable on rootless runtimes
- fix: `init --force` confirmation in fail-closed test so the agent Containerfile is actually created
- refactor: extract `cli/lib/runtime.sh` — all container commands go through runtime helpers with cached resolution
- refactor: rename `DOCKER_TTY_FLAGS` to `TTY_FLAGS` across `run.sh` and `config.sh`
- docs: update architecture, configuration, and README for runtime-agnostic terminology
- test: add unit coverage for runtime resolution, rootless detection, engine validation, and `fifo_is_healthy`
- test: update all integration and failure tests for runtime-agnostic container commands
- test: 316 tests, 18 suites, 100% function coverage (69/69)
## 0.8.0
- feat: add `carranca kill` to stop one exact session or all active Carranca sessions after confirmation
- feat: add shared session lifecycle helpers so `run`, `status`, and `kill` use the same container teardown logic
- fix: stop interrupted `carranca run` sessions cleanly so agent and logger containers do not remain running after `Ctrl+C`
- docs: update README, architecture, configuration, and session-log docs for session lifecycle and `kill` behavior
- test: add unit and Docker integration coverage for session helpers and `carranca kill`
- test: 278 tests, 18 suites, 100% function coverage (48/48)
## 0.7.0
- feat: add `carranca status` to show active sessions and the 5 most recent session logs for the current repo
- feat: add `carranca status --session <exact-id>` for detailed per-session status, including active state, summary, touched paths, and commands
- docs: update README command docs for the new `status` command and detailed session mode
- test: add unit and Docker integration coverage for status overview, detailed session output, recent-session limits, and missing-session failures
- test: 253 tests, 15 suites, 100% function coverage (39/39)
## 0.6.0
- feat: move project config to ordered `agents:` entries only and drop legacy single-agent config support
- feat: add canonical `--agent <name>` selection for `carranca init`, `carranca run`, and `carranca config`
- feat: add `carranca config --prompt <text>` so free-form operator requests are passed into the config-agent prompt
- fix: report the actual selected agent name in `carranca run` session output instead of the container name
- fix: install the Codex CLI in this repo's Carranca agent container so the configured `codex` agent runs successfully
- docs: update README, configuration, architecture, and session-log docs for multi-agent config, command help, and improved `carranca log` output
- docs: add repository `LICENSE` and Carranca artwork used in the README header
- test: expand unit, integration, and failure coverage for agent selection, prompt plumbing, and `agents:` validation
- test: 214 tests, 13 suites, 100% function coverage (35/35)
## 0.5.0
- feat: add `carranca log` to pretty-print the latest or selected session log for the current repo
- feat: improve `carranca log` with unique-path counts, file-event totals, top touched paths, and clearer agent-native edit summaries
- feat: add command-specific help routing via both `carranca help <command>` and `carranca <command> help`
- test: add unit, failure-mode, and Docker integration coverage for `carranca log`
- test: add coverage for help routing, sparse-session summaries, and log parsing helpers
- test: 191 tests, 13 suites, 100% function coverage (27/27)
## 0.4.0
- feat: add `carranca config` to launch the bound agent, apply `confiskill`, and propose `.carranca.yml` and `.carranca/Containerfile` updates
- feat: run `carranca config` with the same interactive TTY flow as `carranca run` so cached agent auth/session state is reused
- feat: mount Carranca-managed and user-managed skills separately inside the agent container
- fix: keep `carranca config` propose-only by sourcing built-in skills from the Carranca install instead of mutating the workspace before confirmation
- fix: validate unsupported `agent.adapter` values early and require a detected stack summary in generated proposals
- docs: document the `config` workflow, skill mounts, and interactive adapter behavior
- test: add integration, failure-mode, and unit coverage for `carranca config` and adapter resolution
- test: 132 tests, 10 suites, 100% function coverage (18/18)
## 0.3.0
- feat: switch the default agent command and project scaffold to Codex
- feat: run the agent container as the invoking host user and preserve supplemental groups
- feat: move the default agent home and cache mount to `/home/carranca`
- fix: make the shared logger FIFO writable across container user boundaries
- fix: install `bubblewrap` in Codex agent images for sandboxed execution
- test: cover host uid/gid mapping, supplemental groups, agent home, and FIFO permissions
- test: 108 tests, 9 suites, 100% function coverage (17/17)
- docs: update configuration and architecture docs for the Codex-based runtime defaults
## 0.2.0
- feat: persistent agent home directory across sessions (`volumes.cache` config)
- feat: custom volume mounts via `volumes.extra` config (e.g. SSH keys, reference docs)
- feat: YAML list parsing in config (`carranca_config_get_list`)
- feat: auto-detect TTY for non-interactive environments (tests/CI)
- fix: inline YAML comments no longer break config value parsing
- fix: logger graceful shutdown — `docker stop` (SIGTERM) instead of `docker rm -f` (SIGKILL)
- fix: logger read timeout allows SIGTERM trap to fire between reads
- fix: shell_command and agent_stop events now reliably appear in session logs
- feat: pre-commit hook auto-updates README test/coverage badges
- feat: test runner enforces 100% function coverage
- test: add unit tests for `carranca_log`, `carranca_die`, runtime helpers, hooks, badge update
- test: 93 tests, 9 suites, 100% function coverage (17/17)
- docs: document cache volumes, custom volumes, and badge workflow
## 0.1.0
- feat: add `carranca init` to scaffold project config with `--claude` and `--codex` flags
- feat: add `carranca run` to start interactive containerized agent sessions
- feat: two-container architecture (agent + logger) connected via FIFO, no compose
- feat: structured JSONL session logging with monotonic sequence numbers
- feat: shell command capture via instrumented shell wrapper
- feat: file mutation capture via `inotifywait` (best-effort, Linux)
- feat: fail-closed logging — agent stops if logger dies (broken pipe detection)
- feat: append-only log protection via `chattr +a` (when `CAP_LINUX_IMMUTABLE` available)
- feat: heartbeat mechanism (30s interval) to detect dead logger between commands
- feat: logger healthcheck gates agent startup (no FIFO race condition)
- feat: repo identity via git remote URL hash (12-char, with path fallback)
- feat: user-configurable agent Containerfile per project (`.carranca/Containerfile`)
- feat: `.carranca.yml` per-project configuration with runtime, policy, and watched paths
- feat: default `/plan` skill for docs-before-code workflow (removed in 0.15.4)
- feat: install/state/config separation (`~/.local/share/`, `~/.local/state/`, `~/.config/`)
- feat: Alpine-based container images (~7MB base)
- feat: interactive agent TTY via `docker run -it` (supports TUI agents like Claude Code)
- chore: add Makefile with lint, test, check, build, install, hooks, version targets
- chore: add ShellCheck linting for all bash scripts
- chore: add git pre-commit hook (validates semver + runs lint + unit tests)
- chore: add GitHub Actions CI (lint, unit tests, integration tests)
- chore: add `.gitignore`, `.editorconfig`, `.shellcheckrc`, `.yamllint.yml`
- docs: add `doc/` with architecture, configuration, session-log, trust-model, versioning
- docs: add CONTRIBUTING.md with project conventions
- test: add unit tests for config parsing, identity, and utilities
- test: add integration tests for init and run lifecycle
- test: add failure mode tests for preconditions and degraded mode