drix10/agent-flow
Run AI coding agents unattended, safely: Implement → Review → QA as separate agents, a guard that blocks edits they must never make, and context that stays true to the code.
Changelog
All notable changes to this project are documented here. Format: Keep a Changelog. Versioning: SemVer.
[Unreleased]
Lean mode, a session briefing, more hosts and a safer uninstall, and the bugs found while reviewing the rest of the code. What is and isn't claimed for lean mode: docs/LEAN.md.
pipeline.lean(off,liteby default,full): the Implementer and Reviewer are told to make the smallest correct change. Atlite: reuse before writing, fix the root cause (grep every caller), leave one runnable check for non-trivial logic, mark a deliberate shortcut with alean:comment and list it in the report, never lean away validation, error handling, security or accessibility. Atfull: also climb the ladder (needed at all, repo, standard library, platform, installed dependency, one line, minimum) and the Reviewer blocks an avoidable new dependency.skills/implementer/references/native-first.mdis a lookup of what the platform, the standard library and the database already do. Read from the default branch, so a branch can't switch it off for its own review. The reviewer's lens tags findingsdelete/stdlib/native/reuse/yagni/shrinkand reportsnet_lines_removable; the implementer report gainsshortcuts, whichrun --prlists in the pull request. Whether it shrinks what a model writes in a given repo is not measured here.agent-flow debtreads everylean:comment (aponytail:one is read too) back from the code with its ceiling and upgrade trigger, flags those that name none, and has--jsonand--fail-on-no-trigger.statusshows the count; the Gardener gains/debtand/audit-lean(and prompts for both). A marker inside a string literal (a fixture) is text, not a comment.agent-flow uninstall [--harness] [--yes] [--keep-runtime] [--keep-hook]takes out whatinstallwrote and nothing else. Previews until--yes. Unedited skills, agents, rules files, the OpenCode plugin and the vendored runtime go; an edited one is kept and named; only agent-flow's own entries leave the hook files (your other hooks and settings stay, an unparseable file is untouched); a skills folder shared by several harnesses stays until the last one goes;AGENTS.md, the manifest, CODEOWNERS, the baseline and the pipeline's state are never touched. A pipeline role can't run it.- A session briefing.
agent-flow briefsays what is protected, review-only and always blocked, which checks must pass and how many issues wait on a person: facts only, never an issue's free text.installwires it asSessionStart(andSubagentStarton Claude Code) for Claude Code, andSessionStart/sessionStartfor Codex and Cursor;--no-briefleaves it out,updateadds it to an existing install. It answers in the shape each harness reads (and Copilot, Qoder and ZCode if one is wired by hand), never reads stdin, never fails. agent-flow statuslineis one line for Claude Code'sstatusLine(guard on or OFF, how many need you, are ready, are working);install --statuslinesets it only when none is set.agent-flow mcp, a zero-dependency read-only MCP server on stdio: status, pipeline state, classify, debt, doctor, audit summary, gates and the brief, as tools and a prompt, for any MCP client. Every tool is one of a short list of read-only CLI commands with validated arguments.pipeline.isolate_roleslaunches each role with--setting-sources project,local, so a plugin or hook installed globally on the machine can't add context to a role. Off by default.- More hosts:
install --harness cline|kiro|qoder(a rules file, plus skills in.agents/skills) andswival|factory|commandcode(skills folders), instruction-tier and listed as such in the harness matrix, with paths that were not run live here. - The guard no longer hangs on input that never arrives. A wrapper that swallows the piped JSON used to leave the hook waiting until the harness killed it, which lets the call through. It waits 10 s (
AGENT_FLOW_HOOK_STDIN_MS) and refuses wherever protection is configured; the stop gate and the badge are bounded too (FM-22). - Fixed: re-running
installstacked another guard hook for Codex, Cursor and Gemini when the runtime was vendored (the "is this ours" check only knew thenode_modulespath), so the guard ran once per install on every tool call. A re-install now replaces ours, andupdateleaves a customised guard hook alone with a note. - Fixed:
install --harness claudeoverwrote a user's own hook whose name merely containedagent-flowandguard(a./scripts/agent-flow-guard.shinPreToolUsewas replaced by agent-flow's command). "Ours" now means "runs agent-flow's own binary" (the project copy or the vendored runtime), for install, the briefing,uninstalland the status badge alike. - Fixed:
updatetreated every harness that shares.agents/skillsas installed (it would have written rules files for ones nobody installed); installed harnesses are now told apart by the install record and their own wiring. The install record no longer forgets what a sibling harness wrote. - Fixed: piped output could be cut short. The CLI called
process.exit()right after a large write (status --json,classify), which can truncate output on a pipe; it now lets stdout drain first. - Fewer tokens spent on agent-flow's own instructions (see docs/LEAN.md): skill descriptions 3,285 to 1,969 characters; the orchestrator skill 15.2 KB to 3.9 KB with its manual procedure moved to
references/manual.md; the implementer, reviewer and QA skills tightened, and told to be terse; QA keeps the first 50 and last 100 lines of a long log; the starterDOCS_INDEX.mdand the rules files shortened; agents are told to runagent-flow briefinstead of reading the manifest; the MCP server cuts answers at 20,000 characters.tests/token-budget.test.jspins the ceilings. doctorwarns when a context file is heavy: an.mdcontext file over 150 lines or about 9 KB is named with its size in tokens (a warning, never a failure;context_weightin--json). Bootstrap now asks for under 60 lines per module.- Fixed: the MCP server's input buffer was unbounded. A client line with no end grew memory without limit; it is now refused past 1 MB and dropped to its newline, a batch is capped at 50, and the next request still works.
- Fixed:
briefloaded the manifest twice (once per trust level) and now once; thestatusshortcut scan has a 1.5 s budget and says when it is partial; a pre-record install in the shared skills folder is read as the genericagentstarget; the review-violation message names the CRLF trap beside the missing-final-newline one. - Fixed (review comments on the pull request): added lines that couldn't be read (a diff past the buffer) were treated as none, so a risky CI line could pass unchecked; they are now a
review_violationsentry.check-versionslet a release tag that isn't shaped like a version (v1.2) through to publish; every tag that isn't the version now fails. A test left its polling timer running on early exit. - Release hygiene:
scripts/check-versions.mjs(every version file, the CHANGELOG section and the docs' action tag agree; on a release tag it equals the tag) runs in CI and before publishing;tests/release-hygiene.test.jspins the load-bearing rules in every skill, prompt and harness copy, and the frontmatter every skill needs.
[1.2.3] - 2026-10-04
Unattended runs no longer stop at every CI change. Found while working out how an agent should get a new test into a CI list without calling a person back for something small.
review_paths(manifest): paths agents may change by adding lines, usually.github/workflows/. The guard lets a role edit them (the one entry of its agent-config list that a manifest can open: skills, hooks, harness settings, the trust files, the manifest andprotected_pathsstay shut, and a path in both lists is protected). The change is classifiedmediumwithhuman_approval_required, so the pull request is a draft that waits for a person and is never auto-merged (review_requiredin Needs Me, with the files to read); the run itself carries on through review, gates and QA instead of stopping. Default is none:initsuggests it (and no longer suggests.github/workflows/as a protected path, which would stop every run), and bootstrap asks once.- Shape check on the diff: an edited or deleted line, a binary change, or an added CI line that reaches secrets (
secrets.*,secrets: inherit), uses the job token, runs onpull_request_targetorworkflow_run, widens a permission (… : write), uses aself-hostedrunner, pipes a download into a shell, pulls in a third-party action (uses:outsideactions/and./), or drops event text into a script (${{ github.event… }}) is areview_violationsentry.runhands it to the Implementer as findings (like apolicyrule) and escalates at the round cap; it can add a line instead of changing one. CI code runs when a pull request opens, before anyone reads it, which is why the second list exists. It is a pattern list, not a sandbox: keep deploy secrets in environments that need approval. - A branch can't widen it.
review_pathsis read from the default branch's manifest (else the last committed copy), likegatesandpolicy;trustedManifestsays when a working-copy edit is ignored, and a deleted or emptied working copy doesn't close it either. The Pi guard re-reads the manifest every 5 seconds as well as when the file changes, since the committed copy changes on a commit without touching the file. - A command that names a review-only workflow (
git add .github/workflows/ci.yml) is judged by what it resolves to, but only a listed pattern that itself sits in the workflows folder vouches for the mention: a broad one such as*.mdcan't launder a workflow file named beside it. classifyprints the review-only paths and violations;statusshows how many there are and, for finished work waiting on review, says to merge it and close the issue instead of sending it back;runsays the same. Skills (implementer, reviewer, orchestrator, bootstrap) andSETUP.mdsay what is allowed and what to escalate.
Found by reviewing the rest of the code while doing this:
- A hostile or huge role output could hang report validation. Finding the last JSON object in free text rescanned to the end from every
{, which is quadratic: a few hundred thousand unclosed braces from a role (or a log full of them) ran for minutes. It now reads only the last 2 MB and stops after a fixed amount of work. - Gate logs no longer pile up. Every gate run left up to 5 MB per gate, and a stop gate runs on each stop. Once
.agent-flow/gates/holds more than 500 files, those older than 30 days are deleted (never this run's). atomicWriteleft a partial.tmpfile when the write itself failed (a full disk); it now removes it. The Pi guard's root cache no longer grows with every directory a session visits.- Test fix: the fake npm registry in
tests/update.test.jsprinted its port withconsole.log, which colours numbers whenFORCE_COLORis set and made the URL invalid.
[1.2.2] - 2026-10-03
Found by running agent-flow run unattended on a real repository (Hypothesis Arena, Windows):
- Script files no longer bypass the guard. The guard checked a command but not a script file the command ran, so an edit to a protected path could be put in a file and run from there. A script that differs from the default branch's copy (new, edited, committed only on the issue branch, or outside the repo) is now checked like the command it contains. The repo's unchanged scripts stay trusted, so gates and tests run as before.
- Prose no longer looks like a protected path. On Windows and macOS, a Python heredoc saying "the stage's rules" was blocked as a write to the protected file
STAGE, which pushed agents toward the script-file workaround. In program text a protected name now counts only as a path in a string literal. - The rule files are guarded. The Implementer can't edit
CONTEXT_MANIFEST.json. A diff that changes it or.risk-baseline.jsonis classified critical, so it needs a person and is never auto-merged.agent-flow codeownersanddoctornow cover both files. - Merged work can be marked done. Once a pull request was merged and its branch deleted,
state update --state Completedparked the issue in Needs Me (unreviewed_commits). The approved commit now counts as the tip when the default branch holds it, either as an ancestor or as a squash merge with the same patch. A different change merged under the issue still does not count. - Usage limits wait instead of parking the issue. A role that hits "You've hit your session limit · resets 5:10pm (…)" used to go to Needs Me and stay there.
runnow waits for the reset and runs the role again, up to three times and only within--limit-wait <hours>(default 6, 0 never waits). Further away than that, it escalates asusage_limitwith the time to re-run. doctornames tests no gate runs. Where a gate lists a directory's tests one by one (for t in test_a test_b; do …), a test file in that directory that no gate lists is reported. That is how a new test never ran and a deleted one stayed listed. A gate that hands the whole directory to a runner is not second-guessed.- The audit log is anchored off the machine.
.agent-flow/audit.jsonlexists only where the pipeline runs, so anyone who could write it could rewrite the whole chain consistently. A pull request opened byrun --prnow carries the log's head hash, andaudit verify --anchor <hash>proves the local log still contains it.
[1.2.1] - 2026-10-02
runroles can read their issue folder and worktree. An Implementer thatcds into its worktree lost Claude Code's permission to readissue.md(3 of 4 live runs on a real repository stopped with a permission error).runnow passes--add-dirfor the issue folder and the worktree to every role.
[1.2.0] - 2026-10-02
agent-flow runis the short path from task to checked work. Give it a testable task or issue number. Claude Code runs implementation, mechanical risk classification, review, required gates and QA in separate processes. By default the result stays in a local worktree;--prpushes and opens a PR, while--auto-mergeis an additional opt-in gated bypipeline.auto_merge_low_risk.--dry-runshows the plan and setup gaps without launching roles. Invalid manifests, missing GitHub auth and invalid timeout values are caught before role calls.agent-flow statussummarizes repository readiness across protection, context, checks and work waiting on the user.- Task text is XML-escaped before it is placed inside the untrusted issue boundary. Auto-merge defaults off in the CLI, even when the repository allows it.
- Updated Quickstart, README and orchestration skill to make the CLI the first path on Claude Code and keep the harness-specific skill procedure for other agents.
- Added end-to-end fake-agent coverage for the run loop, resumptions, report correction, risk escalation, gate failures, protected paths, QA tree mutation and PR creation.
- Resume is trustworthy. A saved report is reused only if its role finished before the phase the issue stopped in, so sending an escalated issue back to
implementruns every role again instead of replaying a rejected result. A resumed round gets the report that ended the previous round as its findings (it used to getnone). Once QA passes the issue is checkpointed aspublish, sorun N --prafter a local run publishes without paying for another review or QA. - One run per issue. A lock in the artifacts folder refuses a second
runfor the same issue and takes over a lock left by a dead process. - Ctrl-C is safe. An interrupted
runstops the agent and everything it started (the whole process tree, on Windows and POSIX), releases the lock, and says how to continue. A timeout now stops the tree too, not just the agent. - Checks on what the Implementer hands over. Uncommitted work is sent back as findings (it would be reviewed here but missing from the pushed branch), and a branch with no changes escalates as
no_changesinstead of opening an empty pull request. - QA mutation check no longer holds files in memory. Modified and untracked files are hashed in 1 MiB pieces; the staged blobs and
git statusare included. The manual skill's shell snippet uses the same fingerprint (withgit hash-object, so it works on macOS). - Windows launch fixes. A line break in a prompt ended the
cmd.execommand line and silently dropped the rest; it is now a space. Validator text is flattened and bounded, a session id is used only if it is a plain token, and QA command lists over 800 characters are passed in a file (a command line is limited to about 8,000 characters). - Task and state fixes. New task text replaces files left by an earlier attempt that never started (it used to inherit the old task), issue numbers skip any with saved artifacts or a worktree, a budget stop is reported as
budget_exceededinstead of a round cap, and the pull request targets the base branch by name (notorigin/main). - Found by running it live on a real repository (Hypothesis Arena, a C++/Python repo with 12 protected paths and 7 critical areas, on Windows):
- On Windows, Claude has a separate
PowerShelltool. The roles allowed onlyBash, so the first command was denied and a turn wasted, and the read-only Reviewer's deny-list named onlyBash, so it still had a working shell (a live call confirmed it ran a PowerShell command under plan mode). PowerShell is now allowed for the Implementer and QA and denied to the Reviewer, inrunand in the manual launch recipe. - A Node
DEP0190deprecation warning printed at the start of every real run on Windows (the preflightclaude --versioncheck passed arguments alongsideshell: true). - Preflight now names the manifest problem ("missing
context_filesarray") instead of only counting it. - Resuming an issue whose work is already reviewed, gated and tested takes seconds and costs nothing: it no longer re-runs the gates (2.5 minutes there) or announces "implementing" for work it isn't doing.
- A critical change used to finish with the same line as a trivial one. It now prints the risk and says to read the diff before publishing. When
pipeline.models.high_reasoningis not set,runandstatussay the critical review runs on the same model as everything else instead of implying a stronger one (a configured tier is passed as--model, verified live: Sonnet for the Implementer, Opus for the critical review). statuscalls an issue that passed review, gates and QA "ready" (with the command to publish), not "in progress".agent-flow state dismiss --issue N --reason "…"lets a person drop an issue they handled by hand, or no longer want, from the list. Its files and the audit trail stay; it is refused while a run is working on it and is a person-only action (every role is refused, the orchestrator too). Before, such an issue stayed in "needs you" forever.- The guard's message for a command that edits files and also names a protected path now says to run the read or the run of that path as a separate command, instead of only "escalate". The Implementer in the trial recovered on its own, but a model told to escalate may not.
- On Windows, Claude has a separate
- Found by independent review of the above: Ctrl-C during a blocking git, gate or push call now stops the run at once (it used to read as a failed gate and use up a round). The run lock is created already holding its content, so a second process can't mistake it for a leftover, and it is refreshed every 30 seconds, so a long run is never taken for a dead one (a lock nobody has refreshed for six hours is stale; a taken-over lock is never deleted by its former holder). Files a gate or QA writes are no longer blamed on the next round's Implementer. Files from an abandoned attempt at a round are set aside as
*.previnstead of outranking the new attempt's findings. A laterrun N --prkeepsCloses #Nfor a GitHub issue.%NAME%in a prompt is no longer expanded bycmd.exeon Windows, and QA commands containing%, quotes or line breaks travel in a file. A late error from a role that is already running no longer discards its result. - Output.
--jsonkeeps stdout a single JSON document (progress goes to stderr, and a missing-setup error is JSON too).statusprints the full command to send an escalated issue back, and describes auto-merge as the opt-in it is. Cost is shown with its$.
[1.1.7] - 2026-10-02
Found by asking how a vendored install ever learns about a newer version (it didn't).
agent-flow update [--yes] [--check] [--force]. Brings the skills, reviewer agents, hook wiring and the vendored runtime up to the running version.installnow records what it wrote (<harness dir>/agent-flow-install.json);updatereplaces only files that still match that record, keeps and lists anything you edited, never downgrades, and previews unless given--yes.--checkexits 10 when an update is available, for a scheduled CI job (a ready workflow is indocs/ADOPTION.md). The vendored copy can't update itself and prints thenpx @drix10/agent-flow@latest update --yescommand. A repo installed before this release has no record, so its first update needs--forceonce.doctortells you when agent-flow is behind, as one dim line for humans at a terminal: when a newer CLI finds an older vendored runtime (no network), or when the npm registry has a newer version (one GET, 2.2 s cap, cached for a day in~/.agent-flow/). Never in--json/SARIF output, CI, the guard or the pre-commit hook;NO_UPDATE_NOTIFIER=1,AGENT_FLOW_NO_UPDATE_CHECK=1,AGENT_FLOW_OFFLINE=1and--offlineturn it off. The GitHub Code Owner lookup added in 1.1.6 now also skips itself in CI.- Docs now say what the network does.
README.mdandSECURITY.mdclaimed "no network calls"; both are corrected to name the two optional, read-only lookups indoctor(npm registry version,ghbranch-protection). updateis a setup action likeinstall: no pipeline role may run it. It leaves a customized guard hook alone (with a note) unless--force, treats hook wiring that is out of date as an update on its own, and compares the OpenCode plugin with the same hash formatinstallrecords. Fixed on the way: a dry run of the vendoring step no longer stops the real copy.- Install-style table in
docs/ADOPTION.md("Keeping it current"): npm, npx and vendored, and how each is told about and applies an update.
[1.1.6] - 2026-10-02
Found by running bootstrap on a real C++/Python repo with no package.json, on Windows.
- Any language, no npm.
installcopies a ~0.5 MB runtime to.agent-flow-runtime/for the guard hooks (Claude, Gemini, Codex, Cursor) when agent-flow isn't in the project'snode_modules, or with--vendor. It used to refuse and tell you tonpm install -D. Commit the folder; the pre-commit hook finds it too. The guard treats it as hook wiring, andaudit-risk,scanandclassifyignore it. The skills no longer claim a devDependency. installkeeps.agent-flow/(audit log, gate logs, backups) out of git through.git/info/exclude, and its next steps say what to commit.- Behavior change on Windows: string gates run in Git Bash, not
cmd.exe. Gate commands come from CI files and READMEs, which are POSIX; undercmd.exethey failed and looked like test failures. A gate written forcmd.exe(.\scripts\check.bat,set X=1 && …) now needsAGENT_FLOW_SHELL=cmd.exe, or anos-specific rewrite. Details: String gates went throughcmd.exe, so every POSIX command (./build.sh, aforloop, the commandsscanreads from CI) failed and looked like a test failure.AGENT_FLOW_SHELLoverrides. A command the shell itself can't start is now an environment error (exit 2); a 126/127 from inside a script is still that script's failure. oson a gate (["linux"]): elsewhere the gate is reported as skipped, never as a pass, and CI judges it. For builds that need a Linux toolchain or a POSIX filesystem.agent-flow manifest sync [--yes]rebuildscontext_filesfrom the context files on disk (newAGENTS.mdfiles, install'sCLAUDE.md, the paths their prose names) and keepsprotected_paths,risk_boundaries,gatesand every other key. Bootstrap and the gardener use it instead of editing references by hand. Roles withoutbootstrap_writecan't run it.scanfinds commands in CI workflow steps, including each plain line of arun: |block (loops, continuations and lines with variables are left out), and inbuild.sh/test.sh, so a repo with nopackage.jsonor Makefile no longer reports none. Three or more sibling commands collapse into one…/<name>.py (N files)line.- CODEOWNERS without the friction. A missing or partial
CODEOWNERSis now one quiet note, not a warning: the guard and pre-commit hook already protect against local agents, and CODEOWNERS only matters for pull requests pushed from elsewhere.agent-flow codeowners [--yes] [--owner @x]previews, then appends the lines (owner fromorigin); an existing file is appended to, never replaced. When the lines are in place and a logged-inghis available,doctoralso reports whether GitHub really requires Code Owner review (rulesets and classic protection); with nogh, no login or no network it says nothing, never asks for a token, and--offline/AGENT_FLOW_OFFLINE=1skips the call. Solo repos are told that requiring it blocks their own PRs. doctor --allow-stalereports stale context without failing, anddoctor --allow-stalereports stale context without failing (for CI on every push; broken paths, schema problems and placeholders still fail). Without it, a repo's CI went red 30 days after setup with no change.- The OpenCode plugin finds the CLI relative to itself (the vendored runtime or
node_modules) instead of the absolute path of whichever copy raninstall, so it works in every clone. - Bootstrap skill: the secrets gate offers fake / real / keep-flagged instead of only stopping; a new gates step (where each can run, timeouts,
on_stop); glob and highest-level-wins rules spelled out;manifest syncforcontext_files; Phase 6 runs the gates and sorts every failure; CODEOWNERS and CI steps without Node in the project; the repo's own rules bind bootstrap. - Guard: an unquoted glob that expands to an env file or a
deny_readpath (cat .e*) is a secret read, like naming the file. - Guard: a recursive search (
grep -r,rg --hidden) over a directory that holds an env file or adeny_readpath is a secret read;rgwithout--hiddenskips dotfiles as it does itself.
[1.1.5] - 2026-10-01
-
Read each vendor's hook docs and source and fixed what they contradicted: Gemini's hook now matches every tool (the allow-list named a tool that doesn't exist and missed others); Cursor's Windows BOM on stdin no longer makes the guard fail open, and Cursor's Delete tool counts as a write; the OpenCode plugin is one flat file with no SDK import (v1 and v2 load it; it no longer writes
.opencode/package.jsonor depends on@opencode/plugin).docs/HARNESS-MATRIX.mdsays, per harness, what is live-verified and what is docs-verified only, with the caveats each vendor's docs give (untrusted folders, fail-open exits, headless modes). -
Codex guard hook live-verified on Linux/WSL (protected write,
--no-verifycommit and hook-config write all blocked); docs note that Codex's bypass-hook-trust / full-access options disable enforcement. -
Guard: PowerShell writes are judged like their POSIX twins — named parameters in any order (
-LiteralPath,-Destination, …), Windows\paths,Copy-Itemwrites only its destination, and .NET[IO.File]::Write*calls count as writes. A live Codex-on-Windows probe foundSet-Content -LiteralPath .codex/hooks.json …slipped through. -
Guard blocks also print a JSON deny (
permissionDecision) on stdout besides exit 2 + stderr; setAGENT_FLOW_GUARD_JSON_ONLY=1to deny by JSON with exit 0 (experiment for a harness that ignores exit 2). -
agent-flow sandbox [--ro] [--no-net] [--hide-home] [--allow <dir>] -- <cmd>runs a command under bubblewrap (read-only filesystem except the worktree), an OS-level boundary the hook cannot give. Linux/WSL only. -
doctorwarns (never fails) whenprotected_pathshave no CODEOWNERS entry, since only the host can stop a pull request editing them. -
Guard: recognises Codex/OpenCode patch payloads, Gemini
replace, argv-form shells,workdir/dir_path, and protects the Gemini/Codex/Cursor/OpenCode hook wiring from edits. -
install --harness gemini|codex|cursoralso installs the guard as a pre-tool hook; the guard understands Cursor's tool-lessbeforeShellExecution/beforeReadFilepayloads. Live verification pending. -
policy.deny_commands: ["infra"]shorthand accepted (it used to load no rule at all). -
install --harness opencode: OpenCode guard plugin (tool.execute.before), fails closed. Live verification pending. -
Guard:
mv x ~/no longer flagged when the repo lives under the home directory (found by a live OpenCode run).
Added
- Cross-vendor roles.
pipeline.harness_by_role({"reviewer": "codex"}) runs a role on another harness than the orchestrator's; the orchestrator skill reads it intoenv.sh, the verdict still goes through the schema, round cap and audit chain, and a missing CLI is Needs Me, not a silent fallback. On the last allowed round the Implementer usespipeline.models.high_reasoning. policy.deny_commands(opt-in): presetsdatabaseandinfraplus custom regexes that the guard refuses in every agent session, with an additive floor from the default branch and the usual human override.- Bounded stop gate for interactive Claude Code sessions. Gates marked
on_stop: truerun from a Stop hook (agent-flow gates stop, installed withinstall --harness claude --stop-gate) when the tree changed since the last pass. It holds a session back at mostpipeline.max_stop_blocks(default 2, max 5) times per turn, then lets it stop and recordsstop_gate_exhausted. Gates come from the default branch's manifest;.agent-flow/stop-gate.jsonand.agent-flow/gates/are tamper-proof. - Per-issue cost cap.
pipeline.max_cost_usd: once an issue's role runs have cost that much,state update(and the Pistate_updatetool) escalates the next new phase or round to Needs Mebudget_exceededwith the cost per round (exit 3 in the CLI). Counts only harnesses that report cost;audit summarynow shows how many runs reported none. doctorchecks commands, links and commits, not just paths.npm|pnpm run <script>andnpm testagainst the nearestpackage.json,make <target>against the Makefile,just <recipe>against the justfile (each with a did-you-mean), relative markdown links (case-exact) and commits cited ascommit <sha>. Skips what it can't resolve: workspace/-Cflags,cd, variables, yarn/bun binaries, shallow clones, fixture directories. New SARIF rulesbroken-linkandunknown-commit.- Verdicts are bound to the commit they judged.
role_runandgate_runaudit lines record the tip ofagent/issue-N.state update --state Completedexits 3 and records Needs Meunreviewed_commitsunless the round's approved review, passed QA and every required gate name the current tip. No skip flag. Runs recorded before this version carry noheadand aren't compared.
Changed
gates,policy,secret_scanandpipelineare read from the default branch's manifest (falling back to the last committed copy), not the working copy an agent can edit;risk_boundariesmay be added to but not removed.gates listandcheck-stagedsay when the working copy differs. A human iterating locally can setAGENT_FLOW_TRUST_WORKING_MANIFEST=1. This corrects 1.1.4's "a branch can't edit the gate that judges it", which held for gates only on the main checkout.
Security
- The guard now refuses
rm -rf ~,$HOME,/wherever the repository is (catastrophic-delete) and commands that build words from${IFS}(obfuscated-command). The red-team corpus grew by 30 cases drawn from gstack'scareful, block-dangerous-git and the new presets, and a test asserts blocks exit 2 through the real hook, never 1. git push origin HEADreached the default branch.HEAD,@, an empty target and dynamic targets (HEAD:$(…),HEAD:refs/heads/$B, globs) now get the sameexplicit-refspecblock as a baregit push, because the guard can't see which branch is checked out. Name the branch:git push origin agent/issue-N.- Roles could spawn agents through the harness's own tool.
Task,Agent,subagentand similar tools are refused for every role except the orchestrator, matching the shell rule forclaude -p. - Read-only roles: more write paths recognised. Archive extraction (
tar x,unzip,7z x,gunzip…),awkredirects andsystem(),php -rwrites,sqlite3mutations andpython -cwithos.system/subprocess. Listing an archive (tar t,unzip -l) stays allowed.
Fixed (docs)
HARNESS-MATRIX.md: the "Protected paths" row showed ✅ for harnesses that only have the pre-commit hook; it now says so. The Codex reviewer cell no longer claims a live write-block probe that was not run. The Gemini reviewer cell and.gemini/agents/reviewer.mdnow matchlaunch.md(default approval mode; Gemini has noplanmode in this version), and the matrix states that Gemini's Implementer and QA run underyolo.- README: "separate processes" is qualified with "when the pipeline runs as documented" (FM-18).
[1.1.4] - 2026-09-29
Two reviews of the guard (one on a repo with an append-only ledger, frozen specs and secrets; one on the fixes to that) closed 16 of 43 attack cases that used to pass, and made agent-flow safe to adopt in a repository that already has rules, a task file and paths that must not change. Every row has a regression test: docs/AUDIT-v1.1.md #77–95.
Added
- Tamper-evident audit log. Every
.agent-flow/audit.jsonlline carriesprevandhash(SHA-256 over the previous hash and the line), written under a lock.agent-flow audit verify [--anchor <hash>]reports the first edited, deleted, inserted or reordered line and exits 1;audit headprints the hash to record in a commit or CI;audit summarycounts guard blocks per role and rule, escalations, rounds per issue and role-run cost. Logs written before 1.1.4 verify as "from before chaining". Tamper-evident is not tamper-proof: anchor the head somewhere the agent can't write. - Gates. A manifest
gateslist (name,commandas a string or an argv array,cwd,timeout_seconds,expect_exit,required) that the orchestrator runs withagent-flow gates run [--name a,b] [--issue n]. Each run leaves a log, its SHA-256, a JSON report and an audit line; exit 1 = a required gate failed, 2 = a gate couldn't run at all (fix the environment, don't spend a review round). The manifest is read from the main checkout, so a branch can't edit the gate that judges it, andreviewer/qacan't rungates run. The orchestrator skill runs gates before QA. - Manifest
policyrules.max_changed_files,max_diff_lines,forbid_patterns(a regex over added lines, optionally scoped to paths) andrequire_tests(changes under these paths need a change under those).classify --fail-on-policychecks the branch diff, including untracked files; the pre-commit hook checks the staged content.classify --fail-on-heuristicfails when norisk_boundariesexist, so CI can refuse to trust a path-name guess. - SARIF 2.1.0.
doctor --sarifandaudit-risk --sariffor GitHub code scanning (file and line locations, no secret values). action.yml. A composite GitHub Action (uses: Drix10/agent-flow@v1.1.4) that runsdoctor,audit-risk --fail-on-newandclassify --fail-on-protected --fail-on-policy, and can upload SARIF. It has not run on GitHub yet: the commands it calls are covered by tests, the YAML wrapper is not.- Docs can't drift from the CLI.
npm testfails when a doc or skill names anagent-flowcommand or flag that doesn't exist, or a backticked repo path that isn't there.
Security
- Protected directories.
dir/**matchesdiritself, andrm/mv/find -deleteof a protected directory or any parent of one (up torm -rf .andrm -rf .git) is blocked for every session. Brace expansion,git -C dir, andtar -C/unzip -ddestinations are resolved like any other write. A directory too large to inspect is assumed to hold a protected path. - Whole-tree git rewrites (
reset --hard,clean -f,stash,checkout ./-f,restore ., and targetedcheckout/restore/rm/mvthat reach a protected path) are blocked whileprotected_pathsis set. - Guard wiring, hooks and manifest are off limits to every session:
.git/hooks/*,CONTEXT_MANIFEST.json,.claude/settings*.jsonand the installed agent-flow can't be edited, deleted, moved orchmod-ed by an agent (a human overrides withAGENT_FLOW_ALLOW_PROTECTED=1).chmod -x fileis parsed as a mode, not an option. - Protection is a floor.
protected_pathsanddeny_readcommitted toHEADor to the default branch still apply if the manifest is deleted, emptied, or weakened on a feature branch. - The guard fails closed on its own errors (unreadable input, a crash) whenever a manifest exists on disk or at
HEAD, orAGENT_FLOW_GUARD_STRICT=1; the Pi hook does the same. Only an unconfigured session is left alone. The guard no longer throws on a command whose first word isconstructor, which used to fail open. - Secrets stay out of context. Real env files (
.env,.env.local,.env2,.env_prod,prod.env, …) and manifestdeny_readpaths can't be read by an agent through the shell (cat,grep,source,< .env,--env-file=,curl -F f=@.env,git show HEAD:.env,mv/ln/cp) or a read tool;AGENT_FLOW_ALLOW_SECRET_READ=1(a human) lifts it. The installed hook matcher now includesRead,NotebookRead,GrepandGlob, so the rule actually runs in Claude Code. - Remote pushes. Git-host MCP tools (
push_files,create_or_update_file,delete_file) can't write to the default branch or with no branch named, are checked against protected paths, and pipeline roles can'tmerge_pull_request. - A gitignore-style
protected_pathsentry with a leading slash (/config/) matches; it used to protect nothing.bootstrap_writerefusesAGENT_STATE.mdand manifestprotected_paths, like every other write tool. - The secret scanner finds credentials assigned to secret-named variables (
APCA_API_SECRET_KEY=…,FRED_API_KEY=…,password = "…") while skipping placeholders, config references and identifiers.
Changed
- Existing files are preserved.
bootstrap_writeon an existing file keeps its line endings, BOM and manifest indentation, saves a backup under.agent-flow/backups/, shows the human what is removed, and refuses a manifest that dropsprotected_paths,risk_boundariesor any other existing key.repairkeeps the manifest's indentation and line endings and takes a lock.install --harness claudekeeps a user's other hooks in a shared matcher group, won't import a symlinkedCLAUDE.mdinto itself, and refuses a malformedhooksshape.hook installtreats only its own header as "ours" and backs up a foreign hook on--force.initno longer generates anAGENTS.mdbeside aCLAUDE.md/GEMINI.md/.cursorrulesthat already holds rules, and suggestsprotected_pathsfrom directories that exist (it writes none). - Secret scanner allowlist.
agent-flow:allow-secreton or above a line, orsecret_scan.ignore_pathsin the manifest, exempts a known fake (agents writing files can't use the marker). The pre-commit check reads staged files through onegit cat-file --batchinstead of a process per file. - Scans use git's file list when it is available (tracked plus untracked-not-ignored files), so untracked gitignored trees are not walked: a scan of a repo with a 7 GB ignored
data/directory no longer takes minutes. Outside git, or when the root isn't the repository's top level, they fall back to a directory walk. Tracked files are always scanned. Ignored env files are no longer reported as "present in the working tree". - CI checkouts.
classifyfalls back toorigin/<default>when a detached PR checkout has no local default branch;doctorfinds context files that are symlinks to a file in the repo (links out of the repo are ignored). scanrecognises C/C++ (CMake, ctest, GoogleTest, Catch2), PHP, Swift, Elixir and Dart.state_updateboundsphase(80) andreason(2000) for the CLI too;reopencan't targetCompleted.tests/redteam/corpus.json: a table of blocked, allowed and documented-gap cases thatnpm testruns, and.pre-commit-hooks.yamlfor pre-commit.com users.- GitHub Packages publish runs the test suite first, like the npm publish.
Fixed
- Paths inside the repo that begin with
..(..data/) are no longer treated as outside it. schema/reportwith a prototype-named role (toString) and a barestate update --roundare usage errors instead of a crash / a silent round 1.pyproject.tomldependencies after an extras bracket (requests[security]) and[project.optional-dependencies]are audited.- Repeated
git -C a -C baccumulates; the git file list keeps literal backslashes in POSIX names and drops paths that leave the repo through a symlinked directory.
Docs
- New docs/ADOPTION.md: what agent-flow writes and when, layered adoption, what the guard does not do, and which backstops belong outside the agent. README, SECURITY, FAILURE_MODES, HARNESS-MATRIX and TRUST_LOOP describe the Claude Code
agent-flow guardhook, what the audit log records and the real install targets; ROADMAP lists what command-text analysis cannot close (OS sandbox, orchestrator-run gates, tamper-evident audit log, run budgets, richer policy, SARIF/Action) instead of implying it.
Upgrading from 1.1.2
- Re-run
npx @drix10/agent-flow install --harness claudeto widen the hook matcher (reads and MCP tools) in an existing.claude/settings.json; your other hooks are kept. - Agents can no longer read env files by default. If a workflow needs it, launch with
AGENT_FLOW_ALLOW_SECRET_READ=1, or keep the file out of the guard's path list by design (deny_readis opt-in for other paths). - While
protected_pathsis set,git reset --hard,git stash,git clean -fand friends are refused for agents; name the files, or use the human override. - Committing a weaker
CONTEXT_MANIFEST.jsonno longer lowers protection; lowering it takesAGENT_FLOW_ALLOW_PROTECTED=1and a merge to the default branch. initandscanskip untracked gitignored trees; tracked files are still scanned.- New manifest keys (
gates,policy) are optional; a manifest without them behaves as before.doctornow reports a malformedgatesorpolicyas a schema problem.
[1.1.2] - 2026-09-28
Security
- Bare
git push(no refspec) is blocked: git would have pushed the checked-out branch, default branch included. Dry runs and tag-only pushes are unaffected. git fetchwith a<src>:<dst>refspec is blocked: it rewrites local branches while looking like a read. Plain fetches only move remote-tracking refs and stay allowed.git update-refand non-listgit replaceare blocked: direct ref and object rewrites outside every branch protection here.
Fixed
state show/state updatewith a bare--issueno longer silently target issue 1; a missing--manifestvalue no longer leaks aTypeError;--reasonkeeps values containing=.repairStaletypes extensionless paths by the filesystem instead of the name.- Launch commands use only flags the supported CLIs offer; Codex's strict-schema output and read-only sandbox, and Pi's read-only tool list, verified against live sessions.
- Root
plugin.jsonversion corrected to 1.1.1; removed a README badge pointing at a directory the package was never listed in.
Full list with a regression test per row: docs/AUDIT-v1.1.md rows #67–76.
[1.1.1] - 2026-09-28
A pre-integration review (three independent reviewers plus a QA sweep of ~200 everyday commands) found and fixed audit rows #37–66: guard bypasses and false positives, a fail-open on an unparseable manifest, a lock race, and a Codex/Gemini pipeline that would not have run. Details and a regression test per row: docs/AUDIT-v1.1.md.
Added
- Zero-setup
doctor. With no manifest it finds every context file (AGENTS.md, CLAUDE.md, GEMINI.md,.cursorrules, Copilot and Windsurf rules), checks every path they mention, and suggests the likely rename ("did you mean …?"). agent-flow initwrites a starter manifest (and an honest AGENTS.md skeleton if there is none) without an LLM.- Runnable pipeline on four harnesses: strict JSON schemas for Codex (
schema --strict), Gemini envelope unwrapping, background role runs with timeouts, crash resume, idempotent PRs, per-launch budgets and an audit line per role run (report --harness). - Unknown commands, flags and harness names get a "did you mean" instead of a help dump.
Security
- No agent session, pipeline role or not, may skip hooks (incl. abbreviated
--no-verif), force-push, delete remote branches or push to the default branch. - Read-only roles can't mutate pipeline state through the CLI twins; one allow list serves the shell guard and the CLI.
- A present-but-unparseable manifest fails closed for writes instead of disabling protection.
- Non-ASCII filenames no longer skip the pre-commit hook;
.risk-baseline.jsonis tamper-proof; implementer shell writes are checked against the worktree (best-effort).
Fixed
- Guard false positives: commit messages, echo text and grep patterns naming a protected path;
git config; read-only git/tee/curl forms for reviewer and QA. withLockrace and crash-left empty locks; prose drift false positives; typed manifest validation;max_review_roundsmust be 1–5.
[1.1.0] - 2026-09-27
A self-audit found that several documented guarantees were prose rather than code, and that the code had security bugs. This release fixes them. A second, adversarial pass before the first tag found five more (round-cap bypass, a glob-matching gap in the guard, a lock-staleness race, and two minor path/regex bugs) plus a deploy gap where two GitHub-native files had silently reverted to their v1.0.2 content, and a Windows-only CI failure traced to a missing .gitattributes. Full list: docs/AUDIT-v1.1.md.
Security
- Fixed shell injection in
worktree_create.baseBranchwas interpolated into a shell string. All git calls now useexecFilewith validated refs. - Fixed path traversal in
bootstrap_write. Writes are confined to the repo, can't escape through symlinks, and are limited to context-file types. - Removed
npx ctxlint, which could download and execute a package. A locally installed ctxlint runs only on request. - Confirmation is real.
CONFIRM_*strings (which the model could read and supply itself) are replaced by a Pi UI dialog. Headless writes needAGENT_FLOW_HEADLESS_WRITES=1. - Claude Code and Gemini reviewer definitions no longer grant a shell. A shell can write files, so "read-only" wasn't true. Codex reviewer uses
sandbox_mode = "read-only"viacodex exec --sandbox read-onlyor a--profile(see below — the initial[agents.reviewer]config.toml syntax was corrected before release). - Secret detection in the risk audit, bootstrap and pre-commit hook. Values are never printed.
state_update's round cap could be bypassed.reopen: truerewound the round counter on any session, not just aCompletedone — a way to dodge the auto-escalation toNeeds Methat rounds are supposed to guarantee. Now gated on the session actually beingCompleted.- The guard's shell check missed leading-wildcard
protected_paths(*.env, a globbedsecretspattern) because it only matched a pattern's literal prefix, which is empty for those. Now matches any literal chunk of the pattern. withLockcould break a live holder's lock, not just a crashed one's — it only checked file age, with no way to tell the two apart. The lock now carries its holder's PID and is broken the moment that PID is confirmed dead, with age kept only as a fallback.
Added
- Guard (
extensions/guard.ts). Pitool_callhook with role-based enforcement viaAGENT_FLOW_ROLE:- reviewer/qa read-only;
- implementer worktree confinement;
- protected paths;
- tamper-proof state files;
- no
--no-verify, force-push, push to the default branch, re-roling or nested agents. Closes FM-16 on Pi.
risk_classifytool andagent-flow classify. Mechanical risk from the real diff (replaces a JS snippet the model was asked to "run").agent-flowCLI (zero dependencies):doctor,audit-risk,baseline accept,classify,check-staged,state,worktree,scan,install --harness,hook install.- Pre-commit hook: protected paths, secrets, broken context references.
- Prose drift detection: every
backticked/pathin context files is checked. Checks are case-exact on Windows and macOS. schemas/context-manifest.schema.json. The manifest is validated on write (FM-17)..agent-flow/audit.jsonl: guard blocks, confirmations, state transitions.- FM-19 (prompt injection), FM-20 (unattended writes), FM-21 (parallel state corruption).
Changed
- Context files are now
AGENTS.md(root + per module). No harness auto-loadsRoot_AGENT.md. Claude Code imports it throughCLAUDE.md. - State machine validates transitions, keeps rounds monotonic, auto-escalates above
pipeline.max_review_rounds, requires a reason for Needs Me, and records history. Writes are locked and atomic, at the main repo root. - Risk audit:
- every dependency is a surface, so new dependencies are detected (v1.0 missed them in existing manifests);
- parses npm, pip, pyproject, Go, Cargo and Bundler manifests;
- word-bounded, line-level patterns;
- nested
node_modulesand tests are ignored; - the baseline is never silently wiped.
- Worktrees:
- the default branch is detected (not hardcoded
main/develop); - leftover branches are reused;
- removal refuses to discard uncommitted work and keeps the branch;
- the list comes from git;
.worktrees/is excluded locally.
- the default branch is detected (not hardcoded
- Orchestrator skill:
- separate process per role, with the concrete launch command for Claude Code (
claude -p), Codex CLI (codex exec) and Pi (pi -p) shown side by side, not just documented for Pi; - artifact packets;
- the Reviewer is launched read-only:
--tools read,grep,find,ls(Pi), thereviewersubagent (Claude Code,tools: Read, Grep, Glob),--sandbox read-only(Codex CLI); - QA flake re-run and a tree-mutation check;
- draft PRs for critical changes;
- opt-in auto-merge;
- untrusted-issue handling.
- separate process per role, with the concrete launch command for Claude Code (
install --harnessnow also targetswindsurf, and is tested for every target (claude,codex,gemini,cursor,copilot,windsurf,agents), not justclaude:--dry-runwrites nothing,--forceoverwrites, re-running is a no-op. Claude Code and Codex CLI are the primary, most-tested targets; Pi remains fully supported but is no longer the lead example in the docs. Any other AGENTS.md-reading tool (Aider, Zed, Warp, JetBrains Junie, RooCode, Amp, opencode, goose, and more) already gets drift detection and risk classification from the CLI unmodified — see docs/HARNESS-MATRIX.md.- Implementer skill: commits before diffing. v1.0 diffed first (an empty diff) and committed
diff.patchinto the branch. detect_harnessreports Pi as the runtime and lists configured harnesses, instead of guessing from folder names.pi.extensionsnames the compiledextensions/index.jsexplicitly. Verified with Pi's own loader: 14 tools, 2 hooks, no errors.- Zero runtime dependencies (removed
glob,yaml,zod;typeboxis supplied by Pi). - CI runs on Linux, macOS and Windows. Added
.gitattributes(* text=auto eol=lf) — its absence was lettingactions/checkoutonwindows-latestrewrite every text file to CRLF, which broke an exact\n-anchored regex in the skill-frontmatter test. Windows-only, deterministic, and unrelated to any of this release's actual code; see docs/AUDIT-v1.1.md.
Removed
pi run …npm scripts and skill references.pi runis not a Pi command.- Unsourced statistics from the README and docs.
Migration
See "Upgrading from 1.0.x" in README-COMPATIBILITY.md.
[1.0.2] - 2026-09-26
- Publish to npm and GitHub Packages. FM-16/17/18 documented.
[1.0.0] - 2026-09-26
- Initial release: bootstrap, worktree, state machine, stale detector, risk auditor extensions; six skills; templates; FAILURE_MODES.md.