Initialization Scripts for Agents
Every session that starts cold pays a setup tax: the agent reads the same files, retries the same probes, and rediscovers the same paths. An init script pays it once and writes the answers into state.
Learning Objectives
- Identify the work an agent should never have to redo per session.
- Build a deterministic init script that probes runtime, dependencies, and repo health.
- Persist the probe result so the agent reads it instead of re-running checks.
- Fail loud, fast, and with one place to look when initialization fails.
The Problem
Open a session on a repo the agent has never seen. It guesses the Python version, then guesses the test command. It lists the repo root five times looking for the entry point, tries to import a package that isn't installed, and asks the user where the config file lives. By the time it makes a real edit, ten thousand tokens have gone to setup work that one script could have done.
That script is the fix. It runs before the agent does anything else and writes an init_report.json the agent reads at startup. The agent stops guessing and starts from facts.
The Concept
An initialization script is a deterministic program that runs before the agent touches anything, checks that the workbench is in a usable state, and records what it found in init_report.json. Each check is a probe: a small function that returns (name, status, detail). The agent reads the report instead of rediscovering the same facts every session, which is how the script pays the setup tax once.
What the init script probes
| Probe | Why it matters |
|---|---|
| Runtime versions | A wrong Python or Node version produces silent wrong-version bugs |
| Dependency availability | A missing package found later costs ten times what catching it now does |
| Test command | The agent has to know how to verify its work. If the command is missing, the workbench is broken |
| Repo paths | Hard-coded paths drift. Resolve them once and pin them |
| Environment variables | A missing OPENAI_API_KEY is a known failure surface, not a runtime mystery |
| State + board freshness | Stale state from a crashed session is a footgun |
| Last-known-good commit | The anchor for the handoff diff at the end of the session |
Fail loud, fail fast, fail in one place
When a probe fails, the script halts and surfaces the failure to the human. There is no "the agent will figure it out." Init exists to refuse to start when the workbench is broken, and the failure shows up in one place.
Idempotent
Run it twice in a row and the second run should change nothing except a fresh timestamp. That property is what lets you wire the script into CI, hooks, or a pre-task slash command.
Init versus startup rules
Rules (Phase 14 · 33) describe what must be true before the agent acts. Init is the script that makes those rules checkable. Rules without init collapse into "be careful." Init without rules is a polished failure.
Build It
code/main.py implements init_agent.py:
- Six probes: Python version, listed dependencies (via
importlib.util.find_spec), test command resolvability, required env vars, state file freshness, and a last-known-good diff against a pinned commit. The Concept table lists seven because it also names repo paths, which the demo does not implement, and its "state + board freshness" row is covered here by the single state-file probe. - Each probe returns
(name, status, detail). - The script writes
init_report.jsonwith the full probe set and exits non-zero if any block-severity probe fails.
Run it:
python3 code/main.py
It prints a table of probes and writes init_report.json. On the happy path it exits zero. Otherwise it exits non-zero and lists the failed probes.
Production patterns in the wild
Three patterns separate a useful init script from a ceremony.
Last-known-good commit anchoring. Compare the current commit against an LKG file written on the last successful merge. If the diff exceeds a budget (default 50 files), the script refuses to start until a human ratifies the new baseline. Cloudflare's AI Code Review uses this to scope reviewer agents: every review session anchors against the same last-known-good, so drift never compounds across sessions.
Lock files with TTL. After the first successful probe pass, write a prereqs.lock. Later runs trust the lock for N hours (24h default) and skip the expensive probes. The script reads the lock first, and if it is fresh and the dependency manifest hash matches, it short-circuits. Docker's layer cache works the same way: an idempotent probe plus a content hash means skip.
No network, no LLM, no surprises in the hot path. Init probes are deterministic plumbing. A probe that asks an LLM to classify a failure, or hits an external service to check a license, is a workflow, not a probe. If a probe takes longer than three seconds in a dry run, treat it as a workbench smell and either move it out of init or cache its result.
Use It
In production, the script gets wired in three places:
- Claude Code hooks. A
pre-taskhook calls the init script and refuses to launch the agent if it fails. - GitHub Actions. A
setup-agentjob runs the init script, and the agent job depends on it. - Docker entrypoint. The agent container runs the init script before exec-ing the agent runtime, so logs surface on failure.
The script ports easily because it calls no specific framework. Bash, Make, or a tasks file can all wrap it.
Ship It
outputs/skill-init-script.md is a skill that interviews the project, sorts its setup work into probes, and emits a project-specific init_agent.py plus a CI workflow that runs it before any agent step.
I treat init as the one place where the workbench gets to say no. When a probe fails, I want the session to halt and a human to look, even if the failure seems small, because a missing env var or a stale state file is exactly the kind of thing an agent will paper over if you let it. My bet is that a script which stops the session at the start is a better trade than an agent that limps through ten thousand tokens of guessing and then edits the wrong thing. If a check can't give a plain pass or fail without a network call or an LLM, I'd keep it out of init and run it as a separate workflow.
Exercises
- Add a probe that diffs the current commit against the last-known-good commit and refuses to start if more than 50 files changed.
- Wire the script to write a
prereqs.lockfile and refuse to start if the lock is older than seven days. This is a hard expiry that forces a human to look, separate from the 24-hour window in which a fresh lock lets probes be skipped. - Add a
--fixflag that auto-installs missing dev dependencies but never modifies runtime dependencies without approval. - Move the probes from hardcoded functions to a YAML registry, then defend the trade-off.
- Add a timing budget per probe. A probe that runs longer than three seconds is a workbench smell.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Probe | "A check" | A deterministic function returning (name, status, detail) |
| Init report | "Setup output" | JSON written next to state holding the probe results |
| Idempotent | "Safe to re-run" | Two runs in a row produce identical reports, timestamp aside |
| Fail loud | "Don't swallow" | Halt and surface to the human, with no silent fallback |
| Setup tax | "Bootstrap cost" | The tokens the agent spends each session rediscovering the obvious |
Further Reading
- Anthropic, Effective harnesses for long-running agents
- GitHub Actions, composite actions for setup
- microservices.io, GenAI dev platform: guardrails (pre-commit + CI checks as init)
- Augment Code, How to Build Your AGENTS.md (2026): init expectations
- Codex Blog, Codex CLI Context Compaction: session start as compaction-aware init
- Phase 14 · 33: the rule set this script enables
- Phase 14 · 34: the state file this script seeds
- Phase 14 · 38: the verification gate the init script feeds
- Phase 14 · 40: the handoff that consumes the init report's last-known-good
Code
- main.py (python, 232 lines)