Samples

Four agent specifications you can apply against a real model.

Rendered from samples/README.md in the repository.

Each subdirectory is a project: a folder with a .agent-container/ directory holding the YAML the CLI reads. That folder is the sample. It is plain text, git-trackable, and it is what agent-container apply reconciles — nothing here is generated at run time.

samples/01-workspace-write/
└── .agent-container/
    ├── environments.yaml            the spec: name, host, container, task,
    │                                credentials, egress
    └── sample01.config/pi/          canonical config, delivered to the agent's
        ├── models.json              home by the <name>.config/<agent>/ convention
        └── settings.json            (used only when agent: pi)
Sample What it declares Needs a repo?
01-workspace-write A headless agent, a task, one credential no
02-egress-boundary The same, plus an egress: allow-list under enforcement: strict yes
03-clone-commit-push repo: clone-on-start, a three-commit pipeline task, a forge token yes
04-avl-tree The hardest task — real software, tests and a TUI yes

Start with 01: it needs only a model key.

Check your CLI is current first#

These samples use the declarative verbs (plan, apply, destroy). An older install will answer no such command — which looks like a broken sample and is not:

agent-container --version          # an install too old to have this also lacks `plan`
uv tool upgrade agent-container    # if it is behind

Or run the repo copy directly: ./bin/agent-container plan.

Once per machine#

./setup-once.sh

The single imperative step, and it exists for a reason worth reading: delivering a credential needs an operator-declared SSH identity (Constitution IX), and the tool deliberately refuses to generate one — a tool-minted key would be a standing credential granting entry to every environment it ever deploys. The script creates one under ~/.config/agent-container/, declares it in settings.yaml and admits its public half in your key collection. Nothing it writes goes into this repository.

Then, per sample#

export ANTHROPIC_API_KEY=sk-ant-...        # or OLLAMA_API_KEY, for agent: pi
export SAMPLE_GH_TOKEN=ghp_...             # samples 02-04 only

cd 01-workspace-write
agent-container plan          # validates and shows absent / matching / drifted
agent-container apply         # converge

plan mutates nothing, so it is always safe to run first. apply is idempotent: a matching spec makes no changes; a drifted one is announced, then recreated.

Samples 02–04 need one edit before they will work. repo: points at a placeholder — change it to a repository you can write to. A throwaway is right; each run pushes a branch. Prefer a private one: a public repo clones happily with a junk token, which would tell you nothing about whether the credential path works.

To run pi instead of Claude Code, change agent: and swap the credential entry. Both alternatives are written out in comments beside the line you change.

No secret is in these files, by construction#

credentials: entries are locators, never values:

credentials:
  - { name: ANTHROPIC_API_KEY, source: env, var: ANTHROPIC_API_KEY }

The variable is read at apply time; the value travels to the container over that container's own sshd and lands on a per-credential volume. It is never written into this file, into the compose model the tool generates, or onto a command line. That is why these samples can be committed and why they ask you to export a variable instead of filling in a blank.

source: also accepts file, keychain, command, onepassword and bitwarden — see docs/agent-as-code.md.

Cleaning up#

agent-container destroy       # removes only what THIS spec declares and owns

When a sample fails#

Not automatically a bug — a model can simply be bad at the task, and 04 is hard for a small one. The distinction:

The specifications in full#

01-workspace-write — A headless agent, a task, one credential#

Browse this sample on GitHub · apply it with cd samples/01-workspace-write && agent-container plan

# Sample 01 — a real agent runs headless and writes to the workspace.
#
# THIS FILE IS THE SAMPLE. `agent-container apply` reads it, validates it, and
# reconciles the environment it declares. Nothing is generated at run time.
#
# To run pi instead of Claude Code, change `agent:` below and swap the
# credential — both alternatives are written out in comments.
environments:
  - name: sample01
    host: local

    container:
      mode: headless          # the agent IS the workload; the container exits with it
      agent: claude           # claude | codex | pi | opencode
      workspace: persistent    # a named volume that survives recreation

      # The task is part of the SPEC, not a flag. It is recorded verbatim in the
      # run record, so never put a credential here — nothing redacts it.
      task: |
        Create a file at /workspace/proof.txt whose contents are exactly:
        SAMPLE-01-OK

    # References, NEVER values. `source: env` reads the variable at apply time;
    # the value travels to the container over its own sshd and lands on a
    # per-credential volume. It is never written into this file, the compose
    # model, or argv.
    credentials:
      - { name: ANTHROPIC_API_KEY, source: env, var: ANTHROPIC_API_KEY }
      # for agent: pi ->
      # - { name: OLLAMA_API_KEY, source: env, var: OLLAMA_API_KEY }

02-egress-boundary — The same, from behind a declared egress boundary#

Browse this sample on GitHub · apply it with cd samples/02-egress-boundary && agent-container plan

# Sample 02 — the same kind of work, from BEHIND a declared egress boundary.
#
# EDIT `repo:` to a repository you can write to before applying.
environments:
  - name: sample02
    host: local

    container:
      mode: headless
      agent: claude
      workspace: persistent
      repo: https://github.com/YOU/YOUR-SCRATCH-REPO    # <-- EDIT ME
      task: |
        In the git repository at /workspace, create a file
        report.md containing one sentence about what this repository holds.
        Commit it with the message 'sample02 report', create a branch named
        sample02-report and push it to origin.

    credentials:
      - { name: ANTHROPIC_API_KEY, source: env, var: ANTHROPIC_API_KEY }
      # The forge token. Delivered as an env var because GH_TOKEN is not a
      # provider API key; the git credential helper reads it for the HTTPS push.
      - { name: GH_TOKEN, source: env, var: SAMPLE_GH_TOKEN }

    # The WHOLE egress policy. Everything not named here is refused at the packet
    # level, in a namespace the agent shares with a sidecar that alone holds
    # NET_ADMIN. Declared WITHOUT a port, so these go to the PROXY allowlist and
    # the agent's TLS is spliced — never terminated, never inspected.
    #
    # github.com is here because this environment pushes. A run that only talked
    # to its model would reach one destination and prove much less.
    egress:
      allow:
        - { provider: anthropic }     # the tool supplies the hosts for a named provider
        - { host: github.com }
        # for agent: pi ->  - { host: ollama.com }
      enforcement: strict             # advisory (default) | strict

03-clone-commit-push — Clone on start, three commits, a forge token#

Browse this sample on GitHub · apply it with cd samples/03-clone-commit-push && agent-container plan

# Sample 03 — clone on start, generate data, transform it, report, PUSH.
#
# EDIT `repo:` to a repository you can write to before applying.
environments:
  - name: sample03
    host: local

    container:
      mode: headless
      agent: claude
      workspace: persistent
      repo: https://github.com/YOU/YOUR-SCRATCH-REPO    # <-- EDIT ME

      # Three commits, not one. A single "write this string" commit proves the
      # plumbing and nothing about the agent — it cannot tell an agent that
      # worked from one that echoed its instructions. Step 2 must READ what step
      # 1 produced and compute over it, so the answer can be recomputed from the
      # pushed data and a fabricated one caught.
      task: |
        In the git repository at /workspace, do the following as THREE SEPARATE
        git commits, then push. Work only inside the directory data/sample03.
        1. Create data/sample03/input.csv with the header line `id,item,amount`
           followed by exactly 8 data rows. Use ids 1 to 8, any item names you
           like without commas, and integer amounts between 10 and 99. Stage it
           and commit with the message 'step1 generate'.
        2. READ data/sample03/input.csv and compute the number of data rows and
           the sum of the amount column. Create data/sample03/summary.md
           containing exactly two lines:
           ROWS=<the number of data rows>
           TOTAL=<the sum of the amount column>
           Stage it and commit with the message 'step2 transform'.
        3. Create data/sample03/report.md with one sentence describing what you
           computed. Stage it and commit with the message 'step3 report'.
        Finally create a branch named sample03-pipeline and push it to origin.

    credentials:
      - { name: ANTHROPIC_API_KEY, source: env, var: ANTHROPIC_API_KEY }
      - { name: GH_TOKEN, source: env, var: SAMPLE_GH_TOKEN }

04-avl-tree — Real software, tests and a TUI — the hard one#

Browse this sample on GitHub · apply it with cd samples/04-avl-tree && agent-container plan

# Sample 04 — the agent writes real software: an AVL tree, unit tests, a TUI.
#
# The hardest sample. A small model will often fail it, and that is information
# rather than a defect.
#
# EDIT `repo:` to a repository you can write to before applying.
environments:
  - name: sample04
    host: local

    container:
      mode: headless
      agent: claude
      workspace: persistent
      repo: https://github.com/YOU/YOUR-SCRATCH-REPO    # <-- EDIT ME

      task: |
        In the git repository at /workspace, write a small Python project in the
        directory avl/sample04. Use ONLY the Python standard library — do NOT
        install or import any third-party package, and do NOT use an existing
        tree implementation; write the AVL tree yourself.
        1. avl/sample04/avl.py — a self-balancing AVL tree. It must define class
           AVLTree with methods: insert(value: int) -> None,
           delete(value: int) -> None, contains(value: int) -> bool,
           in_order() -> list[int] returning every value in ascending order, and
           height() -> int (an empty tree has height 0). Rebalance with rotations
           on both insert and delete. Commit with the message 'avl implement'.
        2. avl/sample04/test_avl.py — unit tests using the standard library
           `unittest` module (NOT pytest). Cover insert, delete, contains,
           in-order order, and that the tree stays balanced when values are
           inserted in ascending order. Run them with `python -m unittest` and
           make sure they PASS before you commit. Commit with 'avl tests'.
        3. avl/sample04/tui.py — a text UI reading commands from standard input,
           one per line: `i <number>` inserts, `d <number>` deletes, `p` prints,
           `q` quits. After each insert or delete print the tree's contents in
           ascending order on one line. Exit cleanly on `q` or end of input.
           Commit with 'avl tui'.
        Finally create a branch named sample04-avl and push it to origin.

    credentials:
      - { name: ANTHROPIC_API_KEY, source: env, var: ANTHROPIC_API_KEY }
      - { name: GH_TOKEN, source: env, var: SAMPLE_GH_TOKEN }