A portable development model for codebases built with the help of AI coding
agents — interactive and unattended alike. It packages ten doctrine principles
(see PRINCIPLES.md) into the small set of files that actually
make them stick: two narrative documents, a handful of skills, a few engine
scripts, a state sandbox, and one safety-critical rule.
Copy-in, repo-owned. You copy this template into your repo and run
./init.sh once. From then on the kit is yours — no external package, no
upstream dependency at runtime. Edit the config, rename things, delete a skill
you don't need. A future packaged version (a plugin plus an installable engine)
waits until the template has proven itself across a few real projects.
A personal note. I built this for my own development work with AI coding agents. The principles and choices here reflect my own preferences and workflows — not a universal best practice. Take what's useful, change what isn't, and shape it to fit how you like to work.
When you build software with AI coding agents — especially several at once, some running unattended, with a single human operator who isn't watching every step — the hard part stops being generating code. It becomes keeping the work coherent. The recurring failure modes:
- Context evaporates between sessions. A fresh session (yours or an agent's) reconstructs "where were we?" from memory or scrollback, and silently redoes or regresses the last one's work.
- Parallel agents step on each other — two lanes writing the same scratch state or the same plan file corrupt each other's output or collide at merge.
- Rough edges get forgotten — the annoyance you hit an hour ago is gone by the next session, so nobody fixes it. Or the opposite: every one-off incident gets promoted into a standing rule until the rules are noise nobody reads.
- Risky changes get rubber-stamped — a send-gate or a destructive operation slips through bundled with cosmetic diffs, or a PR is opened and abandoned mid-CI with no one watching it to green.
- The wrong effort goes to the wrong step — top-tier reasoning burned on a mechanical rename, or a cheap pass on the one decision that was expensive to get wrong.
- Rules a fresh agent "should have known" don't bind it — because they live in a doc nobody re-reads instead of in the launch prompt, a hook, or a CI check.
agentic-dev-kit is a small, opinionated answer to those failure modes: ten
doctrine principles plus the minimum set of files — narrative docs, skills, hooks,
scripts — that make each one stick rather than stay a good intention. It assumes
a single operator, agents working on branches behind pull requests, and review
before merge. Adopt the pieces incrementally; each stands on its own.
One session runs the inner loop — session-start → work → pr-watch → wrap-up — while the friction flywheel turns underneath it, feeding tickets and new rules back into the next session's briefing.
flowchart TD
A([session start]) --> B["session-start<br/>reads handoff + friction-log<br/>+ tracker + open PRs + CI"]
B --> C{"pick next work<br/>by urgency"}
C -->|self-contained| D["parallel<br/>isolated worktree lanes<br/>· cheaper model tier"]
C -->|judgment / interactive| E["cockpit<br/>work inline"]
D --> F["open PR"]
E --> F
F --> G["pr-watch<br/>poll · fix · reply<br/>until green and clean"]
G --> H{"risky change?<br/>send-gate · destructive · kill-path"}
H -->|yes| I["safety-critical review<br/>deterministic gate · dual-lens<br/>· operator sign-off"]
H -->|no| J["merge"]
I --> J
J --> K["wrap-up<br/>update handoff + log friction"]
K --> L([session end])
K -. friction accrues .-> M[(friction-log)]
M -. weekly .-> N["/triage-friction-log<br/>single incident → tracker"]
M -. weekly .-> O["/post-merge-systemize<br/>2+ occurrences → a rule"]
N -. tickets .-> P[(tracker + handoff)]
O -. new rule .-> Q[(agent rules)]
P -. seeds next session .-> B
Q -. binds next session .-> B
Solid arrows are one session's flow; dotted arrows are the asynchronous flywheel (down by default — incidents to the tracker — and up only on repetition — patterns to rules).
Prerequisites. init.sh needs POSIX sh plus the usual coreutils/text tools it
shells out to — awk, grep, sed, mv, rm, cat, head, mkdir, chmod, touch,
basename, dirname, date and git — all standard on macOS
and Linux, none of them a runtime you have to install. The engines need uv (they're PEP-723 single-file
scripts), git, and — for pr-watch / parallel — the GitHub CLI gh,
authenticated. No PyYAML in anything you run: scripts/lib/kitconfig.py, the reader
every engine imports, is stdlib-only. (scripts/lib/devmodel_config.py is
PyYAML-backed, but no engine imports it and the parity tests compare kitconfig against
yaml.safe_load directly rather than against it — so PyYAML is a test-time dependency at
most.)
# Click "Use this template" on GitHub and clone the result — or, into an
# existing repo, copy the kit's contents in from the root:
cp -r /path/to/agentic-dev-kit/. .
./init.sh
# Answer the prompts (or accept the shown defaults), then:
# -> open config/dev-model.yaml and fill in anything you skipped
# -> start your agent session and invoke session-startinit.sh stamps your answers into config/dev-model.yaml, renders the narrative
docs and the root AGENTS.md entry point from docs/templates/, installs the
pre-push hook, and adds four entries to .gitignore — state/, .devkit_state_root,
.claude/worktrees/ (isolated review lenses) and reports/ (derived pipeline output).
It adds a fifth, .mcp.json, only if that file exists and appears to hold literal
credentials rather than ${ENV_VAR} references; it says so when it does. That check
matters because dev_session.sh copies a repo-root .mcp.json into every lane
worktree, so a lane inherits whatever is in it. The sniff is narrower than it looks: its regex is case-sensitive and matches
only _-separated names, so the hyphenated mixed-case shape the kit's own docs use
("CF-Access-Client-Secret") is not detected and .mcp.json stays tracked
(#86, open). Do not rely on it —
prefer ${ENV_VAR} references so there is no literal to leak. It never overwrites a
rendered doc that is already in use — only one whose first line still carries the
shipped devkit-template: unrendered marker (or that is missing entirely). A doc
that merely mentions the marker further down is in use, and is left alone.
Ten minutes, start to finish. For a full worked example of a first session — from
adoption through wrap-up — see docs/getting-started.md.
Pull the new kit files, then re-run ./init.sh. That is the supported upgrade
path, and it is safe to run any number of times:
- Config —
init.shmigrates an older schema forward in place, only ever adding missing keys. Your existing values are never guessed over.kit.versionrecords which generation you're on. paths.engines— probed from where your engines actually are, so a repo that vendored them underscripts/devkit/is migrated to that path rather than a wrong default.- Narrative docs — a handoff or friction log you're actually using is left byte-identical; only an unrendered skeleton is (re-)rendered.
- Hooks — reinstalled as a shim that execs the engine, so a hook stays current with the engine rather than going stale as a copy.
- Engines — replace the files. Engines are kit-owned: everything
project-specific (review-bot markers, informational checks, CI policy, paths)
lives in
config/dev-model.yaml, so you should never need to edit an engine to adopt it. If you have, that's a bug — please report it.
Re-running init.sh handles the config. For the engines, the problem is that a
copy-in has no version marker and no record of whether it was edited, so nothing can
tell an older engine from a locally-patched one:
uv run scripts/kit_doctor.py # or: python scripts/kit_doctor.py # stdlib only, no uv neededPer kit-owned file it reports unchanged (safe to replace outright), differs (diff
before replacing), missing, or unknown-version. differs deliberately does not
claim a cause — a hash mismatch can't distinguish "older version" from "hand-edited", so
it narrows by schema version and leaves the call to you. Adopter-owned paths (your
config, your narrative docs) are never compared; they're supposed to differ.
It also checks four things nothing else validates:
- your config's schema generation vs. the kit's
- that
paths.enginespoints at a directory that actually holds engines — a✗here is the silent breakage where every workflow's<engine-dir>/…reference resolves to nothing - that the pre-push hook is installed, not merely shipped
- that the narrative docs were rendered, not left as unrendered templates
kit-manifest.json is the hash set it compares against, regenerated at release
(--generate-manifest) and gated in CI so it can't go stale.
/upgrade drives the whole sequence — shape detection,
config migration, then per-file refresh keyed on those states — non-destructively, on a
branch. It's the counterpart to /adopt (first install). A
repo with no config/dev-model.yaml at all predates the config surface and routes to
/adopt instead.
The workflow definitions under docs/agentic-dev-kit/workflows/ are shared. The
runtime adapters are intentionally thin:
| Runtime | Repository adapter | Invocation |
|---|---|---|
| Claude Code | .claude/commands/<name>.md |
/session-start, /wrap-up, /pr-watch, /parallel |
| Codex | .agents/skills/<name>/SKILL.md |
$session-start, $wrap-up, $pr-watch, $parallel |
Set runtime.default in config/dev-model.yaml. The lane launcher reads its command
from runtime.launchers; shared workflows use the runtime-neutral
cheap/default/expensive tiers and translate them through
models.runtime_mappings only when the runtime exposes that control.
The quickstart above assumes a fresh or near-empty repo. Dropping the kit into a
mature project — one that already has agent configuration, its own config/, a
plan doc, and CI — needs a lighter touch: a blind cp -r would clobber files. Adopt
selectively instead.
The /adopt skill automates this. Copy
.claude/commands/adopt.md into your repo, run /adopt, and it inspects the repo,
proposes a selective plan (what to install vs. skip vs. point the config at), and
executes it non-destructively on a branch — then seeds the friction log with whatever
the adoption surfaced. The principles it applies:
- Install only what you lack. If the repo already practices a piece — a living plan, its own wrap-up skill — keep its version and skip the kit's. Each principle stands alone.
- Point the config at what's already there. Already have a
ROADMAP.mdor similar plan? Setpaths.handoffto it inconfig/dev-model.yamlrather than adding a second plan file — or rename it tohandoff.mdif you prefer the kit's name. - Don't overwrite existing skills. Check both
.claude/commands/<skill>.mdand.agents/skills/<skill>/SKILL.md. Keep an adopter's existing workflow and install only the missing adapters. - Namespace the scripts if
scripts/is organized. If the repo keepsscripts/in subdirs, vendor the kit underscripts/devkit/(or similar) and setpaths.enginesaccordingly. Every engine discovers the repo root by walking up for.git, so it works at any depth without prompt rewrites. One documented limitation: with no.gitanywhere above it,kitconfigfalls back to depth arithmetic calibrated forscripts/lib/, which is wrong in a vendored layout — issue #60 stays open on it. Any real checkout has a.git, so this bites test harnesses and tarball copies, not adopters. - Check your CI/lint scope. The
state_pathstests use bareassert(they're pytest tests) — make sure a repo-wide lint scopes away from the kit's dir or ignoresS101there.
This path was walked for real: a pilot into a live, mature repo, whose adoption friction became several of the fixes in this version.
Each piece maps to one or more of the ten principles in
PRINCIPLES.md.
| Piece | Principle(s) | Purpose |
|---|---|---|
docs/handoff.md + docs/handoff-history.md |
#1 Living-plan handoff | The one canonical plan — read at session start, updated at session end. Older sessions sweep to the history file once it crosses a line budget. |
docs/friction-log.md + docs/friction-log-archive.md |
#2 Friction flywheel | Append-only inbox for bugs and rough edges, triaged on a cadence: single incidents route down to your tracker, real patterns graduate up into a rule. |
docs/templates/ |
#1, #2 | The .tmpl sources init.sh renders into the four narrative docs above, plus the root AGENTS.md entry point for Codex-run adopters, on adopt or upgrade — never overwrites one already in use. |
scripts/lib/state_paths/ |
#3 Cockpit + isolated lanes | The sandboxed state-path resolver so parallel agent lanes never clobber each other's scratch state. |
docs/agentic-dev-kit/workflows/ |
#1, #2, #3, #5 | Runtime-neutral definitions for session-start, wrap-up, parallel, and pr-watch. |
docs/agentic-dev-kit/workflows/parallel-headless.md |
#3 Cockpit + isolated lanes | Unattended/headless lane launch mechanics split out of parallel.md — the --headless JSON descriptor, the lane-contract preamble, the fan-out recipe. |
.claude/commands/ + .agents/skills/ |
#1, #2, #3, #5 | Thin Claude and Codex adapters over the shared workflows. Claude ships eight commands: the four wired workflows (session-start, wrap-up, parallel, pr-watch) plus triage-friction-log, post-merge-systemize, adopt, and upgrade. |
scripts/check_memory_budget.py |
#1, #8 Mechanism over memory | A SessionStart hook (wired in .claude/settings.json) that warns when an agent-memory file outgrows its budget — the memory-side counterpart to check_doc_budget.py. |
scripts/hooks/pr_followup_hook.py |
#5 PR follow-through | A PostToolUse hook on gh pr * that fires the mandatory watch-to-green loop the moment a PR is opened or readied, so following through is a mechanism rather than a thing the agent has to remember. Reads six config keys: review.bots, review.fallback_commands.claude, paths.engines, review.fallback_panel.lenses, review.fallback_panel.receipt_source and review.fallback_panel.lens_compute.claude. |
docs/AGENTS-sections.md |
#4, #5, #6 | Ready-to-merge persistent instructions for Codex adopters. |
docs/CLAUDE-sections.md |
#4 Merge classes, #5 PR follow-through | Ready-to-paste CLAUDE.md sections: risk-based PR splitting, the mandatory watch-to-green loop, execution rules, the rules-layout convention. |
docs/autonomous-session-playbook.md |
#4, #5, #7 | The full operating contract for operator-requested autonomous sessions — branch hygiene, sequencing, local gate, draft→ready, watch-and-fix to merge, self-merge policy. |
docs/agentic-dev-kit/safety-critical-changes.md |
#6 Safety-critical doctrine | Shared doctrine for send-gates, destructive operations, and kill/recovery paths; bound through the Claude rule and the suggested AGENTS.md section. |
config/dev-model.yaml |
#10 No hardcoding | The single config surface every skill and script reads instead of hardcoding a value. |
scripts/lib/kitconfig.py |
#10 No hardcoding | Stdlib-only reader for config/dev-model.yaml, used where an engine must stay dependency-free (pr_watch.py declares zero third-party deps). |
scripts/check_doc_budget.py, scripts/archive_plan_sessions.py |
#1 | The tripwire and sweep that keep the handoff file from ballooning. Which files are watched — and how big each may get — is the doc_budgets: list in config/dev-model.yaml; each entry is {path, budget, archive, remedy}, and the remedy string is what the warning tells you to run. Warn-only by default — it exits 0 even when a doc is over budget, and returns 1 only under --strict. It exits 2 on a usage or config error whatever the flags, so a path: naming a doc you have since renamed will gate any pipeline that runs it. |
scripts/pr_watch.py |
#5 | The poll-fix-ack engine behind pr-watch. |
scripts/dev_session.sh, scripts/reconcile_sessions.sh |
#3 | Worktree/lane launcher and reconciler. |
scripts/hooks/pre-push |
#8 Mechanism over memory | A hook, not a memory — refuses a push that would corrupt the narrative files. |
Principle #7 (model/effort tiering) is doctrine actually woven into the pieces
above, not just described by them: the tier table lives in config/dev-model.yaml
and travels with each lane through parallel. Principle #9 (deterministic
scaffolding around LLM steps) is only partly real in the shipped kit.
scripts/pr_watch.py's seen-set is the one durable intermediate state the kit
actually ships. The rest of #9's artifacts — a heartbeat, an input cap, resumability,
map-reduce batching — are specified in .claude/commands/post-merge-systemize.md,
but that skill's engine (a tracker client, a merged-PR fetcher, heartbeat_cli.py)
is not shipped, so the doctrine there is aspirational until it's vendored.
Issue #7 tracks vendoring those
engines. Read PRINCIPLES.md for both principles' full statement.
Four workflows ship wired for Claude and Codex; two ship as Claude-side doctrine.
session-start, wrap-up, parallel, and pr-watch come with their engine scripts
and both runtime adapters. triage-friction-log and post-merge-systemize document
the flywheel's
triage and pattern-finding mechanism, but their deterministic engines are
project-specific and left for you to wire — see the banner atop each of those two skill
files. What is missing, precisely — two integrations plus five scripts, every one of the five
verified absent from scripts/: a tracker client, a notify channel, scripts/fetch_merged_prs.py (the forge-API fetcher), scripts/digest_merged_prs.py
(the slimmer that consumes it), scripts/heartbeat_cli.py, and triage-friction-log's own
scripts/triage_friction_log.py + scripts/finalize_triage.py.
Issue #6 tracks the triage engine
behind a tracker adapter; issue #7
tracks the systemize side.
When you want several agent sessions running at once, the kit keeps them from
clobbering each other: one cockpit session owns the narrative files and the
merges, while each unit of work runs in an isolated lane — its own git worktree,
branch, and DEVKIT_STATE_ROOT state sandbox. The rule that makes it safe is
disjoint file footprints: two lanes may run together only when no source file is
edited by both (the sandbox prevents state collisions, not source merge conflicts).
The flow: parallel plan clusters candidate work by footprint → launch a lane per
disjoint cluster (scripts/dev_session.sh new … --merge-class self|operator) → each
lane works to a green, ready-for-review PR → the cockpit reconciles every lane and completes
the recorded merge path. Lanes mark their own PRs ready but never merge: a self-class
lane is closed out by the cockpit through scripts/dev_session.sh merge (no operator
sign-off needed), an operator-class one only by an explicit operator decision.
Full walkthrough — the lane contract, the live board, reconciliation, and a worked
example — in docs/parallel-dev.md. For step-by-step
recipes per use case (and what actually happens when you run each parallel verb),
see docs/parallel-howto.md.
Once you've adopted the kit, it's yours. config/dev-model.yaml is the single
place to point the skills and scripts at your project's paths, tracker, review
bots, and model tiers — start there. Beyond config, edit the skills and scripts
freely: they're prompts and small stdlib scripts, meant to be read and changed.
Run the suite after modifying the engines:
make test # the whole suite; supplies pytest + pyyaml itself via uv
make mutation-test # same, minus the drift self-check — use this when mutating filesmake test is the command, not a convenience wrapper. The two probes you would
otherwise reach for both fail in a way that reads as "pytest is unavailable here" and
is not: uv run pytest → Failed to spawn: pytest (pytest is not a project
dependency), and python3 -m pytest → No module named pytest (the system Python has
none; a bare python may not exist at all, which is a third misleading signal). The Makefile target exists precisely because the bare invocation does not work.
Improvements that would help other adopters are welcome back here.
MIT — see LICENSE.