Skip to main content

seekr-hatchery

PyPI Python License OpenSSF Scorecard

Task orchestration CLI for AI coding agents. Each task gets an isolated git worktree and its own agent session, sandboxed by default inside Docker.

Sandboxing (on by default) — each task runs in full isolation:

  • 🐳 Docker sandbox: the agent runs inside a container with carefully scoped filesystem access — read-only repo, write-access only to its own worktree
  • 🌿 Isolated worktree: each task gets its own hatchery/<name> git branch and worktree, so parallel work never conflicts

Task management — structured workflow with persistent records:

  • 📋 Plan-first workflow: plan → approval → implement → commit; enforced by task files the agent must follow
  • 🔄 Resumable sessions: interrupted sessions pick up exactly where they left off via preserved session state
  • 📄 Task files as records: each task file becomes a permanent ADR in the repo after completion

Codex demo

Installation

uv tool install seekr-hatchery

To upgrade to the latest release:

uv tool upgrade seekr-hatchery

Note: Do not pin a version on install (e.g. ==0.3.0) — uv stores the constraint and will refuse to upgrade past it.

Requires Python 3.12+ and at least one agent:

  • OpenAI Codex: npm install -g @openai/codexcodex on $PATH, OPENAI_API_KEY

Quick start

# Start a new task
hatchery new add-auth

# Start a new task using OpenAI Codex
hatchery new add-auth --agent codex

# Resume an interrupted session
hatchery resume add-auth

# Mark complete and remove the worktree
hatchery done add-auth

# See all tasks for this repo
hatchery list

How it works

hatchery new <name> creates a git worktree on a hatchery/<name> branch, drops a task file there for you to fill in, commits it, then launches an agent session pointed at that worktree. The agent runs inside a Docker sandbox by default — a starter Dockerfile is created automatically on first use. The agent plans, implements, commits, and marks the task complete — all inside the isolated branch. When you're satisfied, hatchery done <name> cleans up the worktree and leaves the branch ready to merge.

Task workflow

When the agent starts a new task it is given a task file at .hatchery/tasks/YYYY-MM-DD-<name>.md. The expected workflow:

  1. Plan first — read the task file, ask clarifying questions, propose a numbered implementation plan. No code until the plan is approved.
  2. On approval — update the "Agreed Plan" section, then implement step by step.
  3. While executing — tick checkboxes in the Progress Log after each step, make a descriptive git commit.
  4. If blocked — stop and discuss before proceeding.
  5. On completion — mark Status as "complete", add a ## Summary section. The task file is merged into main as the permanent record.

Commands

Command Description
new <name> Create worktree + branch, open task file, launch agent
resume <name> Reattach to the existing session exactly where it left off
done <name> Remove worktree, retain branch, mark task complete
abort <name> Remove worktree without marking complete (branch kept)
delete <name> Remove worktree, delete branch, erase all metadata
list List all tasks for the current repo
status <name> Show task metadata and the full task file
self update Upgrade hatchery to the latest release
config edit Open ~/.hatchery/config.yaml in $EDITOR with validation
logs View or follow the hatchery log file (~/.hatchery/hatchery.log)

All new / resume commands accept:

  • --no-docker — skip the container even if a Dockerfile is present
  • --no-worktree — reuse the current directory instead of creating a new worktree

new also accepts:

  • --from <ref> — fork from a specific branch or commit (default: HEAD)
  • --editor / --no-editor — force editor or prompt mode for the task objective. By default, hatchery prompts in the terminal; set open_editor: true in ~/.hatchery/config.yaml to default to $EDITOR. If the editor is opened and the file is unchanged on close, the task is cancelled.
  • --commit / --no-commit — control whether hatchery auto-commits its scaffolding (task file, Docker configuration). Default: from a repo-local .hatchery.yaml (auto_commit: true/false) if present at the repo root, else the global config (auto_commit: true). Use --no-commit to keep all hatchery files out of the tracked repo — task records and Docker files stay at <repo>/.hatchery/ but are hidden from git via .git/info/exclude instead of being committed. Set auto_commit: false in ~/.hatchery/config.yaml to make no-commit the default everywhere, or in a repo's .hatchery.yaml to make it the default for just that repo.
  • --agent [codex] — choose the AI agent (auto-detected from installed agents)

The chosen agent is stored in task metadata and re-used automatically on resume.

Docker sandbox

By default, new and resume build a Docker image from .hatchery/Dockerfile and run the agent inside it. On first new, if no Dockerfile exists, a starter is created for the selected agent and opened for editing.

The container receives:

  • Full repo mounted read-only (for context)
  • .git/objects and .git/logs read-write (so commits work)
  • .git/refs/heads/hatchery/ read-write (own branch ref)
  • The task worktree read-write (the only place edits land)
  • ~/.codex and a per-task auth config — Codex only
  • ~/.gitconfig read-only (commit identity)

A .hatchery/docker.yaml config file is also created alongside the Dockerfile.

Custom mounts (docker.yaml)

.hatchery/docker.yaml controls extra host→container bind-mounts injected on every launch. The file is pre-populated with commented examples — uncomment what you need:

schema_version: 1
mounts:
  # - "~/.kube:/home/hatchery/.kube:ro"
  # - "~/.aws:/home/hatchery/.aws:ro"
  # - "~/.config/gcloud:/home/hatchery/.config/gcloud:ro"
  # - "~/.oci:/home/hatchery/.oci:ro"

Mount format: "host_path:container_path[:mode]" — identical to Docker's own -v syntax.

  • ~ is expanded to your home directory.
  • mode defaults to ro (read-only) if omitted.
  • Invalid entries are a hard error. Paths that do not exist on the host are silently skipped.

The file is tracked in git so every developer on the project gets the same mount configuration. Changes take effect on the next new or resume.

Symlinked directories

A symlink stores a path, and that path only means the same thing inside the container when the mount's destination equals its source. Hatchery mirrors repo paths host→container precisely so that holds for your worktree. It does not hold under $HOME: the container's is /home/hatchery. So if you keep your agent config in a dotfiles repo — ~/.codex/skills/my-skill symlinked to ~/.dotfiles/codex/skills/my-skill — the link dangles in the sandbox and the skill is invisible to the agent, even though skills/ itself is mounted.

The fix is one rule: mount the link's target at whatever path the link resolves to as seen from inside the container. The directory stays bound whole and nothing is mounted at the link's own path, so the link stays a link. Both relative and absolute links work, which matters because tools like GNU stow create relative links by default.

Agent config directories (~/.codex/skills, memories, prompts) do this always. For the worktree and your own mounts: entries it is opt-in, since it costs a walk of the tree per launch: set follow_symlinks: true in .hatchery/docker.yaml.

Three things worth knowing:

  • A mount whose own source is a symlink needs none of this. -v resolves the source path, so a symlinked ~/.codex/AGENTS.md — or a symlinked skills/ directory — already lands on the target's inode. Only links inside a mounted directory survive to dangle.
  • Your dotfiles paths become visible inside the sandbox at their real host paths, since that is where an absolute link expects them.
  • Links are followed at any depth (pruning .git, node_modules and similar), so a real skill directory containing a symlinked references/ works too.

Persistent cache volumes (docker.yaml)

For package-manager caches (uv, pip, npm, …) a host bind-mount routes every cache read/write through virtiofs on macOS, which is slow for many-small-files patterns. Use a named docker/podman volume instead — it lives inside the container engine's storage, persists across --rm containers, and is shared by every sandbox that mounts it:

volumes:
  - name: uv-cache
    path: /home/hatchery/.cache/uv

The volume is auto-created on first launch as hatchery-<name> and re-used afterwards. A bare name like uv-cache is shared across tasks and repos; suffix it (e.g. uv-cache-myrepo) to scope a cache to one repo. To free disk space later: docker volume rm hatchery-uv-cache (or podman volume rm).

Clipboard image paste

Press Ctrl-V in the agent's TUI to attach an image from your host clipboard to the next prompt. Works on macOS, and on Linux with wl-paste or xclip installed — terminal-agnostic. Enabled by default; set clipboard_images: false in .hatchery/docker.yaml to disable.

API key security

The real API key never enters the container. Hatchery starts a lightweight host-side HTTP reverse proxy on an ephemeral port immediately before launching the container.

Codex (OpenAI):

  • OPENAI_API_KEY — a random per-task proxy token
  • OPENAI_BASE_URL — pointing to the host proxy (http://host.docker.internal:<port>)

The SDK inside the container uses these transparently. The proxy validates the inbound token, strips whatever credentials the container sends, injects the real API key in the correct format (Authorization: Bearer for OpenAI), and forwards the request over HTTPS. The real key never leaves the host process.

This means a jailbroken or adversarially-prompted agent that reads its API key env var or attempts to exfiltrate it gets only the proxy token — which is worthless outside the session.

The proxy token is stable per-task (persisted across container restarts) so cached credentials stay valid on subsequent resume launches.

The container's ~/.codex

The sandbox gets its own ~/.codex, backed by a per-task volume rather than your real one. On first launch hatchery seeds it with a config.toml derived from your host config: settings like model, model_reasoning_effort, MCP servers and TUI preferences carry over, while every base_url and experimental_bearer_token is replaced with a placeholder and the per-task proxy token. The agent's working directory is marked trusted so codex doesn't prompt on startup.

Codex owns the file from then on — it can save a default model or a TUI preference and those survive resume. Two consequences worth knowing:

  • The sandbox never writes back to your host ~/.codex/config.toml.
  • Host config edits are picked up by new tasks, not by tasks that already exist.

config.toml is deliberately not a bind mount. Codex saves it atomically — write a tmp file, then rename() over the target — and a rename onto a single-file bind mount fails with EBUSY because the target is a kernel mount point. That is what produced failed to persist config at ~/.codex/config.toml.

AGENTS.md, memories/, skills/ and prompts/ are bind-mounted RW, so those cross task boundaries and stay in sync with the host; model-catalog.json is mounted read-only. The three directories resolve symlinked entries — see Symlinked directories.

Custom Codex providers

If ~/.codex/config.toml configures a custom provider via experimental_bearer_token (any non-OpenAI provider with a static bearer), hatchery routes the host-side proxy at that provider instead of OpenAI. Detection is automatic — there is no flag to set. The bearer token stays on the host: the container only ever sees the per-task proxy token, in the scrubbed config.toml described above.

TLS verification uses the OS native trust store via truststore — macOS Keychain, Linux /etc/ssl/certs, Windows cert store. Any CA already installed system-wide (public or corporate) is trusted automatically. If the upstream presents a certificate signed by a private CA that's not yet in your OS trust store, install it there (the same way you'd install it for curl, your browser, or any other tool) — no hatchery-specific config required.

There is no automatic token refresh — when the host bearer rotates, update config.toml on the host through whatever workflow your setup uses.

Container runtime auto-detection

Hatchery prefers Podman as the sandbox runtime when it is installed, falling back to Docker otherwise. Podman is rootless-native: UID 0 inside the sandbox maps to the calling user on the host — not real root. No daemon required. If you have both installed, podman info is checked first.

Podman-in-Podman (DinD)

DinD enables the agent to run a nested container engine inside its Docker sandbox — useful when your tasks involve building container images, running integration tests with Docker Compose, or any workflow that itself needs a container runtime.

To enable:

  1. Uncomment the ── Podman-in-Podman (DinD) block in .hatchery/Dockerfile. This installs podman, fuse-overlayfs, and uidmap, and wires up a passwordless sudo wrapper so the hatchery user can invoke Podman.

  2. Set dind: true in .hatchery/docker.yaml:

    schema_version: 1
    dind: true
    mounts: []
    
  3. Run hatchery new <name> or resume <name> — the image rebuild is only slow the first time after the Dockerfile change; subsequent runs hit the layer cache.

What you can do inside the container:

# Run as the `hatchery` user inside the sandbox
podman run --rm hello-world
podman build -t my-image .
podman compose up

hatchery automatically provisions .hatchery/seccomp.json the first time DinD is enabled. This seccomp profile allows the extra syscalls required by Podman's user-namespace networking stack.

Session environment

Every agent session launched by new or resume receives two environment variables:

Variable Value
HATCHERY_TASK The task name (e.g. add-auth)
HATCHERY_REPO Absolute path to the repo root

Statusline integration

You can show the active task and its branch in your terminal statusline. Example script:

hatchery_line=""
if [ -n "$HATCHERY_TASK" ]; then
    hatchery_branch=$(git -C "$HATCHERY_REPO" --no-optional-locks \
        rev-parse --abbrev-ref HEAD 2>/dev/null)
    cyan=$(printf '\033[0;36m'); yellow=$(printf '\033[0;33m'); reset=$(printf '\033[0m')
    hatchery_line="├ ${cyan}${HATCHERY_TASK}${reset}  ${yellow}${hatchery_branch}${reset}"
fi

Then output "$top\n$hatchery_line\n$bottom" when $hatchery_line is non-empty, otherwise "$top\n$bottom". This renders as:

┌ user@host  ~/path/to/repo  (hatchery/add-auth ●)  Sonnet
├ ⬡ add-auth  hatchery/add-auth
└ [14:32:01]  [████████░░░░░░░░░░░░] 38%

Storage layout

<repo>/
  .hatchery.yaml           # optional repo-local config override (auto_commit)
  .hatchery/
    Dockerfile             # optional sandbox definition
    docker.yaml            # optional Docker config (custom mounts, etc.)
    tasks/                 # task records: <date>-<name>/task.md
    worktrees/             # active worktrees
                           # commit mode: everything above except worktrees/ is
                           #   committed; worktrees/ is gitignored via .gitignore
                           # no-commit mode: same layout, but the whole .hatchery/
                           #   directory is hidden via .git/info/exclude instead
                           #   (never committed, never edits the tracked .gitignore)

~/.hatchery/
  config.yaml              # user config (default_agent, open_editor, auto_commit)
  meta.json                # DB schema version
  hatchery.log             # always-on rotating log file (5 MB × 3 backups)
  tasks/                   # all per-task state, namespaced by repository
    <repo-id>/             # stable hash of the repo path
      <task-name>/         # one directory per task
        hatchery.log        # per-task log file (during runs)
        meta.json          # task metadata
        proxy_token        # Docker session: stable API proxy UUID
        COMMIT_EDITMSG     # Docker session: git sentinel file
        ORIG_HEAD          # Docker session: git sentinel file
        git_ptr            # Docker session: container-path .git pointer

Logging

Hatchery always writes logs to disk — no flags needed. The file handler captures INFO level by default, so proxy requests, RBAC decisions, and session lifecycle events are on disk even when the console is quiet.

Console output (stderr) is shown during startup (Docker build, volume creation, proxy start) and automatically detached before the agent sandbox launches so it doesn't corrupt the agent's TUI.

Two-tier file logging:

  • Global~/.hatchery/hatchery.log (rotating, 5 MB × 3 backups). Accumulates everything across all commands and tasks.
  • Per-task — when a task launches, a per-task handler is added alongside the global one at ~/.hatchery/tasks/<repo-id>/<name>/hatchery.log. Both files receive all messages during the run. The per-task file is a clean, complete record for that task alone — no cross-task interleaving even if two hatchery spawns run concurrently.

Use --log-level DEBUG to see verbose output on the console (pre-launch) and capture DEBUG in the log file:

hatchery --log-level DEBUG new my-task

Available levels: DEBUG, INFO (default), WARNING, ERROR.

Viewing logs

hatchery logs              # global log (last 50 lines)
hatchery logs my-task      # per-task log
hatchery logs -n 100       # last 100 lines
hatchery logs my-task -f   # follow a task's log (tail -f)

Development

uv sync          # install deps and editable package
uv run hatchery --help

uv run ruff format .
uv run ruff check --fix .
uv run pytest tests

Version is derived from git tags via uv-dynamic-versioning. Without a matching v*.*.* tag it resolves to 0.0.0.dev0.

Contributing

PR title format

The PR title must follow Conventional Commits. The CI validates this on every PR.

<type>(<optional scope>)<!>: <description>

Allowed types: feat, fix, docs, chore, style, refactor, perf, test, build, ci, revert, no-bump

Individual commits on your branch are not validated — use whatever messages work for you while developing.

Merging

All PRs are merged via squash commit. The squash commit message is set to the PR title, which is the only commit that lands on main.

Version bumps

On every push to main the CI computes the next version from the squash commit message and creates an annotated git tag. uv-dynamic-versioning derives the package version from that tag.

PR title prefix Version bump
no-bump: none — skips tag and release entirely
feat!: / any type with ! major — x.0.0
feat: minor — 0.x.0
fix:, perf: patch — 0.0.x
everything else patch — 0.0.x

Most merges produce a release. Types like chore, docs, refactor etc. result in a patch bump. Use no-bump: to land a commit on main without cutting a release (e.g. for CI tweaks or documentation-only changes that do not warrant a version increment).

Examples

feat(cli): add --dry-run flag
fix: handle missing config file gracefully
chore: update ruff to 0.16
refactor(worktree): extract branch-name validation
feat!: rename `new` command to `start`
no-bump: update CI workflow variables

GitHub repository settings (for maintainers)

  • Settings > General > Pull Requests: enable "Allow squash merging", set default commit message to "Pull request title"

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

seekr_hatchery-0.53.0.tar.gz (380.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

seekr_hatchery-0.53.0-py3-none-any.whl (163.2 kB view details)

Uploaded Python 3

File details

Details for the file seekr_hatchery-0.53.0.tar.gz.

File metadata

  • Download URL: seekr_hatchery-0.53.0.tar.gz
  • Upload date:
  • Size: 380.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for seekr_hatchery-0.53.0.tar.gz
Algorithm Hash digest
SHA256 411e1a1729b2622f4c2f428cd94b07f1f4b0dea1ff68942a695d8ecf4019e642
MD5 3487352bb26d9642dd6bd9aec9e52e75
BLAKE2b-256 a1eda9e1849e16f9ff239381664eebb44cbbd12b6bac119de1f4e37c30feb8ec

See more details on using hashes here.

File details

Details for the file seekr_hatchery-0.53.0-py3-none-any.whl.

File metadata

  • Download URL: seekr_hatchery-0.53.0-py3-none-any.whl
  • Upload date:
  • Size: 163.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for seekr_hatchery-0.53.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b4b3a5a0e56057115c190edf911af9ef5453d6a6a19a000f89dd27dcda076cd9
MD5 2313f1a46a554796dd648f1bb70fca02
BLAKE2b-256 8410b8c5c5c672fc9bd6d8c3c736df8387f509240022468321c251a9a7400f78

See more details on using hashes here.

Release history Release notifications | RSS feed

0.55.0

2 files

0.54.0

2 files

This release

0.53.0 This release

2 files

0.52.0

2 files

0.51.0

2 files

0.50.2

2 files

0.50.1

2 files

0.50.0

2 files

0.49.2

2 files

0.49.1

2 files

0.49.0

2 files

0.48.0

2 files

0.47.3

2 files

0.47.2

2 files

0.47.1

2 files

0.47.0

2 files

0.46.2

2 files

0.46.1

2 files

0.46.0

2 files

0.45.0

2 files

0.44.1

2 files

0.44.0

2 files

0.43.0

2 files

0.42.0

2 files

0.41.0

2 files

0.40.4

2 files

0.40.3

2 files

0.40.2

2 files

0.40.1

2 files

0.40.0

2 files

0.39.0

2 files

0.38.1

2 files

0.38.0

2 files

0.37.0

2 files

0.36.1

2 files

0.36.0

2 files

0.35.2

2 files

0.35.1

2 files

0.35.0

2 files

0.34.0

2 files

0.33.1

2 files

0.33.0

2 files

0.32.2

2 files

0.32.1

2 files

0.32.0

2 files

0.31.0

2 files

0.30.0

2 files

0.29.0

2 files

0.28.1

2 files

0.27.2

2 files

0.27.1

2 files

0.27.0

2 files

0.26.0

2 files

0.25.0

2 files

0.24.2

2 files

0.24.1

2 files

0.24.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page