Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MyFlow

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).

Why

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.

One slice

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.

What the controller enforces

  • A gate that cannot be talked round. Gate steps (lint, types, tests, build) come from the project config, each with a level: fast runs on every done, full before every commit, and final_extra_steps (held-out, e2e) once, at finish. 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-commit refuses unless the last GREEN full gate ran on exactly the tree being committed. A gate that ran no test says GATE OK-NO-TESTS, never GREEN; a step that passed only on its rerun is recorded as flaky. On documentation slices, docs_exec re-runs the command/output pairs written in the changed Markdown and fails when the output is stale.
  • Scope by the actual delta. done looks 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) is SCOPE RED; leaving the task's files_hint is 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 above review.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 --defer to 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 CONVERGING with 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 is WRITER 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 wait is idempotent, and a job whose wrapper died while its runner is alive is reported as detached, not as a failure.
  • A guard in the session that asked for it. A PreToolUse hook, 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 finish the controller extracts the costly events of the run from its journal into issues<N>.md. defect writes 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. finish asks for a verdict. retro compares 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.

Repository layout

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_configmf_specsmf_gatemf_taskmf_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.

Requirements

  • 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 -p also 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: pytest and hypothesis.

Install

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.

First run

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.

Choosing models

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.

Unattended mode

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

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.

Tests

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.

Documentation

Limitations

  • 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_new itself, 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.

License

MIT, see LICENSE.

About

A controller for AI coding agents that takes 'done' away from the model: a Claude Code skill plus a stdlib-only Python controller (test gate, scope by delta, tracked review findings, budgets, observability)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages