A controller for AI coding agents that takes the word "done" away from the model.
MyFlow is a Claude Code skill (/myflow_new) plus a controller written in plain
Python (standard library only). Claude Code is the orchestrator: it turns a goal into tasks and drives the
controller's commands. Separate model processes write the code and review it. The controller owns everything that
must not depend on a model's opinion: the order of the steps, the test gate, what a change is allowed to touch,
every review finding and what happened to it, the budget, and the proof that the commit is exactly the tree that
passed the gate.
Status. A personal tool, used every day on the author's own projects since late August 2026 and published as it is. Windows-first. The skill prompt (
SKILL.md) and the full reference (docs/help.ru.md) are in Russian; the controller, its output and the documents linked below are in English. The default model chains name the CLIs and subscriptions the author has, so expect to change them (see Choosing models).
A coding agent writes the code and then decides that the code is finished: it wrote it, it looked at it, it says "done". The checker and the checked are the same party, and on long runs that fails in predictable ways: tests that never ran reported as green, edits made after the review slipping into the commit, review comments going round in circles, a second pass quietly undoing the first, a compacted context that forgot what was decided.
MyFlow separates the two. Between "the model wrote it" and "the work is accepted" there is a program that understands nothing about the intent and does one thing: it runs the checks and counts the result. It does not believe text from a model; it looks at files, command output, exit codes and the hash of the working tree. A model may say "all tests pass" as often as it likes: if no test ran, the controller does not print GREEN, and the next command refuses to run.
Everything else follows from that. The controller keeps its own state on disk, so any session can die and the next one continues from the same point. Work is accepted in slices, and the order of steps is enforced by refusing commands issued in the wrong phase. Automatic checks do not see everything, so a different model reviews every slice, and each finding is tracked as an object with an id. The reviewer can be wrong too, so you can disagree with a finding, but only on the record.
next → workorder → writer → done → review-run → [repair → writer → done → review-run]* → gate --full → git commit → attach-commit
| step | what the controller does |
|---|---|
next |
picks the next task (priority, dependencies, file order) and checks the budget |
workorder |
renders the writer's prompt: requirements first, the files the task may touch, the decisions made so far in this slice, the ids of the open findings |
writer |
runs the writer CLI with retries inside the role; judges the result by the exit code and the working tree, not by the model's summary |
done |
checks the scope of the actual delta, then runs the fast gate (static steps and the tests that belong to the changed files) |
review-run |
builds the review pack (diff, requirements, gate evidence, earlier findings), runs the reviewer chain, records the findings |
repair |
opens another writer pass with the findings and the decision memory of the slice |
gate --full |
runs the full suite and pins the hash of the working tree |
attach-commit |
accepts the commit only if the last GREEN full gate ran on exactly this tree, then closes the task |
A command issued in the wrong phase is refused, with the list of commands allowed in that phase. status says
where the run is; continue prints exactly what can be done next.
- A gate that cannot be talked round. Gate steps (lint, types, tests, build) come from the project config, each
with a level:
fastruns on everydone,fullbefore every commit, andfinal_extra_steps(held-out, e2e) once, atfinish. The hash of the working tree is taken in a private temporary git index before and after each gate: if a file changed while the gate ran, the gate is RED and names the paths.attach-commitrefuses unless the last GREEN full gate ran on exactly the tree being committed. A gate that ran no test saysGATE OK-NO-TESTS, neverGREEN; a step that passed only on its rerun is recorded asflaky. On documentation slices,docs_execre-runs the command/output pairs written in the changed Markdown and fails when the output is stale. - Scope by the actual delta.
donelooks at what changed since the slice started, not at what the task promised; paths that were dirty before the slice are invisible to it. Touching a protected path (existing tests, by default) isSCOPE RED; leaving the task'sfiles_hintis a warning. A legitimate exception is a journaled command:scope allow --paths a,b --reason "…". - Every finding is an object and gets an outcome. Findings carry a stable id (
F-T-07-3), a fingerprint, a severity and a category. Unresolved findings at or abovereview.block_severity(7) block the commit in every profile. Each one ends as repaired,--accept-risk(listed in the final report),--reject(with what you reproduced) or--deferto another task (created with a dependency). A finding that only re-asserts a settled one is advisory. - An independent reviewer. A reviewer running the writer's engine is skipped, and a reviewer that changes the working tree has its findings voided. A second voice is called only for findings that can change the commit decision, with its own arbitration pack, so it does not inherit the first reviewer's framing.
- Review has to converge. From the second pass on, the reviewer must give a verdict on every earlier finding
and sees only the last repair's diff. When new findings keep coming without the severity going down, the run
gets
NOT CONVERGINGwith a diagnosis, and no further repair pass is opened automatically. - Memory across repair passes. The repair prompt carries what was already decided in the slice (accepted,
rejected, deferred, and why), so the next pass does not reopen settled questions. A pass that returns a file to
an earlier state is flagged
REVERT SUSPECTED; a pass that changed nothing isWRITER NO-OP, not progress. - Role chains with failure classes. Writers and reviewers are external processes (
claude -p, the Codex plugin, Grok Build CLI, OpenCode) started by the controller, which retries inside the role (up to 5 attempts, the same session, backoff 20/60/120/240 s) and records tokens and cost. Transport errors, billing refusals, rate limits with a named reset time, a failing foreign hook, a model the account cannot use and a harness limit on background commands are separate classes with separate handling: retry, wait for the reset, skip the candidate, resume the session. None of them counts against the task. - Durable specs. Requirements live in the repository (
docs/specs/, OpenSpec-compatible## Requirement:/### Scenario:/ GIVEN-WHEN-THEN), come first in the writer's work order and go into the review pack, where code that fails a scenario is severity ≥ 6 and a requirement rewritten to match the code is severity ≥ 7. - Budgets. Billable tokens (cache reads excluded) and USD per run, whether the limit is the owner's or the default, a forecast from the average cost of a closed slice, and a warning when the orchestrator's context is close to its window.
- Background work that does not lie. Long writers, gates and reviews run with
--detach;job waitis idempotent, and a job whose wrapper died while its runner is alive is reported asdetached, not as a failure. - A guard in the session that asked for it. A
PreToolUsehook, armed only in the Claude Code session that invoked the controller, blocks running writer CLIs directly (which would bypass retries and accounting), writing the controller's own files, and editing existing tests without a grant. - Friction reports. On
finishthe controller extracts the costly events of the run from its journal intoissues<N>.md.defectwrites an observation about the workflow itself to disk the moment it is made, because auto-compaction would erase it before the end of the run.finishasks for a verdict.retrocompares candidates and friction across all projects and suggests changes without applying any. - An unattended loop with bounds: see Unattended mode.
docs/failure-modes.md lists the failures behind these rules, one by one, as they were met on real runs.
| path | contents |
|---|---|
SKILL.md |
the skill: the orchestrator's procedure (Russian) |
scripts/myflow_new.py |
the controller's entry point: every subcommand, loop, argument parsing |
scripts/mf_*.py |
the layers under it, each importing only the ones before it: mf_util (constants, journal, git helpers, gate lock) → mf_config → mf_specs → mf_gate → mf_task → mf_roles (writer and reviewer runners) → mf_jobs (detached jobs) → mf_hooks |
scripts/myflow_guard.py |
the project hook |
scripts/myflow_loop.ps1 |
a PowerShell wrapper for the unattended loop |
scripts/glm_setup.py |
prepares a separate Claude config directory for the optional Z.ai GLM reviewer |
tests/ |
282 scenarios in 21 files |
docs/ |
architecture and failure modes (English); the full reference and the illustrated walkthrough (Russian) |
observability/ |
optional metrics, dashboards and analytics over the controller's files |
hosts.example.json |
a template for machine-specific tool paths |
About 10,000 lines of Python in total.
- Windows 10 or 11. Process handling, the loop wrapper and the observability scripts are written for Windows; macOS and Linux are untested.
- Python 3.8 or newer and git. The controller needs nothing outside the standard library.
- Claude Code: it is the orchestrator, and
claude -palso serves as a writer and reviewer route. - At least one working writer route and one reviewer route. The defaults assume Grok Build CLI (writer), the Z.ai
GLM Coding Plan through a separate Claude config directory (reviewer, set up by
scripts/glm_setup.py) and the Codex plugin for Claude Code (reviewer and writer fallback; needs Node.js). None of them is required; see Choosing models for a Claude-only setup. - For the tests:
pytestandhypothesis.
git clone https://github.com/brownjuly2003-code/myflow-controller "$HOME/.claude/skills/myflow_new"
The directory name matters: the skill calls the controller as ~/.claude/skills/myflow_new/scripts/myflow_new.py.
Optional: copy hosts.example.json to hosts.json next to SKILL.md and keep only the entries that differ on
your machine (tool binaries, the projects root used by retro, where friction reports go). Without it, every path
defaults to a place under your home directory; friction reports go to ~/myflow-issues.
In Claude Code, in the project directory (a git repository):
/myflow_new Add CSV export to the reports page
The orchestrator onboards the project, writes the run's spec and task list, sets up the gate from the project's own test and lint commands, installs the project hooks and validates the plan. Check the environment and the role routes from the project directory:
python "$HOME/.claude/skills/myflow_new/scripts/myflow_new.py" doctor
Then work in slices:
/myflow_new slice one task, from next to attach-commit
/myflow_new status where the run is (also: continue, stats, reconcile)
/myflow_new finish the final full gate (+ held-out steps), then ACCEPTED and reports/final.md
python …/myflow_new.py --help and <command> --help show the controller's own help.
Each project keeps its role chains in .myflow/config.json → roles. The defaults (v2.4.9):
| role | chain |
|---|---|
| writer | grok (Grok Build CLI) → grokw (the same CLI under a second account) → opus-high (claude -p --model opus --effort high) → terra-xhigh (a GPT model through the Codex plugin) → opus-cli (claude -p, effort xhigh) → sonnet-max (a Claude Code subagent) |
| reviewer | glm-5.3 (Z.ai GLM Coding Plan, claude -p in plan mode under its own config directory) → codex (the Codex plugin) → opus-cli (claude -p --permission-mode plan) → opus-sub (a subagent) |
| reviewer2 | a Claude Code subagent at effort medium; alternative terra-xhigh |
doctor checks the route of every candidate (binary, login, plugin) and reports DEGRADED when a role has no
working non-subagent candidate. Change a chain with roles set; the chosen candidate becomes the first, the rest
stay as fallbacks. A Claude-only setup, from the project directory after the first /myflow_new <goal>:
MF="python $HOME/.claude/skills/myflow_new/scripts/myflow_new.py"
$MF roles set writer --candidate opus-high
$MF roles set reviewer --candidate opus-cli
$MF config set review.allow_same_engine true --new
$MF doctor
The last config set is a deliberate compromise. The controller skips a reviewer that runs the writer's engine
(the same candidate, or the same kind and model family), because a model reviewing its own output is the very
thing MyFlow exists to prevent. With a single vendor you have to allow it explicitly; a reviewer from another
vendor (the Codex plugin, Z.ai) is the better setup and needs no override.
The default model_policy holds the author's rules (no haiku in any role, reviewers at least opus); it is part of
the config. Model names in the defaults are what the author had in September 2026. A candidate is a small dict in
scripts/mf_util.py (executor, kind, model, effort, command template, environment), so adding another CLI route
is a matter of one more entry.
By default the orchestrator works in your Claude Code session and stops when you stop. loop runs the workflow
without you: a supervisor starts one fresh orchestrator process per step (claude -p "/myflow_new continue" with
opus at effort max) until the goal is accepted, then takes the next goal from BACKLOG.md.
That is autonomous work on your accounts, so the controller wants consent in writing. mode unattended, which
loop requires, is accepted only if ~/AGENTS.md (or the file named by MYFLOW_AGENTS_MD) contains a section
with the words MyFlow Exception. The controller checks only that marker; the text is for you and for the models
that read the file. For example:
## MyFlow Exception
Runs started with `myflow_new mode unattended` may continue without me, one step per process,
within the loop bounds. No deploys, no messages to other people, no payments.
The bounds, set in config.loop and overridable per run:
| flag | limits | default |
|---|---|---|
--wall-clock-min |
wall-clock time | 480 |
--max-runs |
how many more goals to take from BACKLOG.md after the current one |
3 |
--max-iterations |
orchestrator processes | none |
--allow-attended |
runs without mode unattended, which removes the consent check: use it only when the owner said so in the current session |
off |
A short leash for the first run on a new project: $MF loop --wall-clock-min 60 --max-iterations 3 --max-runs 1.
The loop also stops on the .myflow/STOP file ($MF stop), on limits.max_slices, after three crashes in a row
without a milestone, when every account is in a usage cooldown, and on a partially completed goal, which always
waits for a human decision. scripts/myflow_loop.ps1 -Project <path> is a PowerShell wrapper around the same loop.
In the morning: reports/final.md (with the accepted risks), blocked.md, stats, git log.
observability/ is an optional Prometheus + Grafana + SQLite (and Superset) stack over the files the controller
already writes: an exporter turns the journals into metrics (events by type, tokens and cost by role, slices,
first-pass rate, gate duration, a stuck-gate signal), Grafana shows a dashboard and three alert rules, and a loader
builds a SQLite database of journal events and Claude Code token usage for month-scale questions. The controller
does not know the stack exists. See observability/README.md.
pip install pytest hypothesis
python -m pytest -q tests/test_consistency.py tests/test_module_layering.py
These two are the structural checks (a few seconds): documentation against code, and the import layering. The whole suite is 282 scenarios in 21 files and takes about half an hour on the author's machine. Run it one file at a time; a single process for all of it has run out of memory. In PowerShell:
Get-ChildItem tests\test_*.py | ForEach-Object { python -m pytest -q $_.FullName }
The scenarios run the controller against temporary git repositories, with stand-in scripts in place of the writer
and reviewer CLIs. The suite passes on the author's Windows machine; a few doctor scenarios also look at the real
CLIs installed there (Grok Build CLI in its default location, for example) and can fail where those are missing.
- docs/architecture.md: how it works, step by step.
- docs/failure-modes.md: the failures behind the rules.
- docs/help.ru.md: the complete reference: every command, flag, exit code and config key, troubleshooting, version history (Russian).
- docs/schema.ru.html: the illustrated walkthrough the architecture document is based on (Russian; open it in a browser).
- One person's tool. The defaults reflect one machine and one set of subscriptions.
- The orchestrator's procedure and the reference are in Russian. Claude follows the procedure whatever language you
write in, and it reads the phrase after
/myflow_newitself, so goals and settings can be given in any language. The older keyword parser (directive) understood Russian phrases only; the entry point no longer calls it. - The controller makes failures visible and keeps them out of commits; it does not make a model better. Review
rounds cost real money: on one run the reviewer at the top effort level was 89% of the bill, which is why
reviewers default to effort
high.
MIT, see LICENSE.