Checking a flow
Check a flow before anything runs it. Two readings, in their order: a static one that runs nothing, then the flow loaded in a subprocess held to a clock — and, when you ask for it, driven by stubs through the worlds worth asking of a loop. Together they catch what otherwise surfaces hours in: the loop nothing can end, the field read off an answer that failed, the name the interface does not answer to.
One line
hmz check official/rlar…/rlar/__init__.py:125: warning: unbounded-loop: every way out of this loop waits for an
agent to say so, and an agent may never say it -- give the loop a bound of its own: a budget
read off spent(), a cap on the rounds, a range
hmz check: 0 errors, 1 warningA flow is named the way -f names one — chat, official/rlar, a path of your own — and everything wrong is said at once, one finding a line. The full table of codes is in the reference.
What an error is, and what a warning is
An error is a flow no run survives: it cannot run, cannot be answered, or cannot end. hmz check exits 1 on any of those. A warning is a flow that runs, and a run of it that may be regretted — rlar's warning above is real and documented: its loop is ended by its reviewer alone, which is the flow's own shape. Warnings print and pass. --strict holds a flow to the whole bar, which is the right setting for a flowverse's CI:
hmz check --strict local/mineThe reading that runs nothing
Pure ast over every file the flow holds. Nothing is imported and nothing is executed, so it is safe to point at a flow nobody has read — one an agent just wrote, one fetched off the internet, one about to be forked:
hmz check --static somebody-elses/flowThe reading that loads it, and the one that drives it
Without --static the flow is also loaded — in a subprocess held to a clock, never in your process — and its live config model is read. That is what the command does. Driving the flow against the worlds worth asking of a loop is the same machinery as a library, and is a call of your own:
from hmz.flows import NEVER_DONE, SILENT, proved
proof = proved(".humanize/flows/mine", scenarios=(NEVER_DONE, SILENT))
assert all(one.finished for one in proof.outcomes), proof.outcomesNEVER_DONE is the reviewer that never says the work is done. The stubs answer every turn at once — every boolean verdict False, every turn adding 100k output tokens to spent() — so a loop held to a budget walks to the end of it in milliseconds, and one whose only exit is the verdict is caught by the turn cap. That is the executable proof that a run of your flow can end. SILENT answers every turn with nothing, which is what a failed turn answers: a flow that reads a field off an unguarded answer falls over here rather than at hour three.
An atlas is read more strictly
A flow marked @atlas gets the stricter of the two readings automatically: its body is a declaration rather than a program, so hmz check compiles it and holds every edge, every branch and every shape to what a graph can be held to. --prophecy prints the graph it compiled; --ship writes it beside the flow for runs of it to walk.
hmz check --prophecy local/mineIn a script
--json says the same findings one JSON object a line, and the exit status is the answer: 0 with nothing blocking, 1 with something, 2 for a line to correct.
hmz check --json local/mine | jq -r .codeSee also
hmz check— the command and its flags- Checking a flow — the library API and the rule table
- Testing a flow — driving a flow with stand-ins of your own
- Writing a flow