Article the third
Two hands on one chart
A statechart is a strange thing to design alone. Its shape is visual: boxes inside boxes, arrows that cross levels, regions that run side by side. But its truth is textual: guards, assignments, event names, the datamodel. People are good at the first and tired by the second. A coding agent is the other way round. This article is about letting each do its half, on one file, with Vertep as the referee.
The arrangement
Three parties work on the same chart file, and none of them owns a private copy of it.
- Claude Code reads and writes the file as text. It drafts states and transitions from a description, writes guards and assignments, renames events everywhere at once, and runs
vtto check its own work. - You look at the chart in Vertepnyk, beside the text. You move things until the shape says what the behaviour is. You spot the missing arrow, rename what reads badly, and run the chart with events to see whether it does what you meant.
- Vertep decides what the chart means. Vertepnyk validates and simulates with Vertep's own interpreter, and Claude checks with the
vtcommand built from the same crates. Neither the agent nor the canvas has its own idea of the semantics.
This works because of one decision in Vertepnyk: the text file is the source of truth. The designer does not keep a model of its own and export it. Every drag, rename or new transition on the canvas is a minimal edit to the file, preserving comments and formatting. So when Claude edits the file, the canvas follows; when you edit the canvas, Claude sees an ordinary diff.
Setting up
- Install the Vertepnyk extension:
code --install-extension pavlyshyn.vertepnyk-vscode, from the VS Code Marketplace, or from Open VSX for Cursor and VSCodium. - Install the
vtcommand, so the agent can check what it writes:cargo install vertep-cli. - Run Claude Code in the project, in the editor's terminal or through its VS Code extension.
- Turn on Files: Auto Save. The designer and the agent meet on disk, and a saved file means neither overwrites the other's unsaved work.
Then tell the agent the house rules once, in the project's CLAUDE.md:
# Charts
- Charts live in `charts/` as SCXML (`*.scxml`) or vertep YAML (`*.vt.yaml`).
- After every edit to a chart, run `vt validate <file>` and `vt typecheck <file>`
and fix what they report before answering.
- Report the chart version from `vt hash <file>` when behaviour changes.
- Never edit `vl:` attributes (SCXML) or the `layout:` key (YAML): that is the
designer's layout. Leave them where they are when you move or rename states.
- Keep comments. Prefer small edits over rewriting the file.
The fourth rule matters. Layout belongs to the person at the canvas, and an agent that regenerates a file from scratch would throw it away.
The loop
1. Say what should happen
Start from words, not boxes:
Write charts/refund.scxml on the CEL datamodel: a refund agent.
It starts idle; a `request` event carries an amount and moves it to review.
In review a policy chooses approve, reject or escalate. Approval is only
possible up to 100; above that the only ways out are reject or escalate
to a manager, who approves or rejects. Make review and manager decide
states for a decider called `policy`. Validate it.
Claude writes the file, runs vt validate, and answers with the chart's version IRI:
charts/refund.scxml: ok: urn:vertep:chart:sha256:c6282b762c855df3792c2fea39fc345df6ed5e317484461e5d0a7f8d68f292aa
The interesting part of what it wrote is short. The rules are in the chart, not in the decider: with an amount over 100, approve is simply not offered in review, whatever the decider would like.
<state id="review" vt:decide="policy" vt:candidates="approve reject escalate">
<transition event="approve" cond="ctx.amount <= 100" target="approved"/>
<transition event="reject" target="rejected"/>
<transition event="escalate" cond="ctx.amount > 100" target="manager"/>
</state>
2. Look at it
Open the file with Open With… › Vertepnyk Designer and run Vertepnyk: Open Text Editor to the Side. Now you have the canvas and the text side by side. Problems from Vertep's validator appear in the Problems panel, and selecting one reveals the element on the canvas.
This is where people earn their place. A chart that validates can still be wrong, and the canvas shows it in a way the text does not: a state with no way out, two arrows that should have been one transition out of a parent, a manager state that ought to be inside a compound escalated state with its own timeout. Arrange the boxes until the picture tells the story. That arrangement is saved as layout in the file, and it is yours.
3. Run it
Start the simulation. Send request with {"amount": 250} and watch review light up. The enabled events are reject and escalate; approve is not among them. Step back, fork the run, and send {"amount": 40} instead. The simulator runs Vertep's interpreter, so this is not a sketch of the behaviour; it is the behaviour.
Runs are where misunderstandings surface. Perhaps you expected a rejected refund to be re-requestable, and the chart ends in a final state. Say so, in the terms the simulator showed you:
In the simulator, after `reject` the chart stops in `rejected` and nothing
is enabled. A customer should be able to send a new `request` from
`rejected`, with the new amount. Keep my layout.
4. Let the agent edit, and watch the canvas follow
Claude adds the transition and validates again. Because the file is the truth, the designer redraws from the saved text: the new arrow appears, and your arrangement stays. Had the canvas held unsaved changes, VS Code would have reported a conflict on save, which is why auto-save is on.
Edits flow the other way too. Rename manager to supervisor on the canvas, and the file changes exactly where that id appears: the state and the transitions that target it. Ask Claude to "follow the rename into the decider configuration and the tests", and it reads the change with git diff like any other.
5. Check what changed, not just that it is valid
A chart's version IRI is the hash of its behaviour, not of its bytes. That gives code review a sharp question: did this change alter what the chart does? Ask the agent to report vt hash before and after. A rename of a state, a new transition or a changed guard gives a new version; reflowed text and comments do not. When the version changes, the next step is to turn the runs you just made into tests, so the behaviour you checked by hand stays checked.
Layout lives in a namespace Vertep reserves for formatting, so moving a box never changes the version. That holds from vt 0.1.4; an older vt reads the layout as content and reports a new version after a drag, so make sure the agent's vt is current (cargo install vertep-cli).
Who is good at what
After a few rounds a division of labour settles in.
- The agent drafts and refactors. It is quick at a first chart from prose, at the datamodel and executable content, and at structural edits that touch many lines: extracting a compound state, renaming an event everywhere, converting SCXML to YAML with
vt convert, adding avt:decidestate with its candidates. With the rule inCLAUDE.md, it runs the validator after every edit without being asked. - You judge the shape and the behaviour. You see the missing exit, the state that should be parallel, the timeout nobody asked for. You run the chart and notice that it does what was written, not what was meant.
- Vertep settles arguments. When the canvas and the agent disagree about what the chart does, neither wins: the interpreter does, the same one that will run the chart in production.
It is the same idea as the goat that does not choose the play, one level up. There, a language model picks among the moves a chart offers while the chart runs. Here, a coding agent proposes changes to the chart itself, and the chart's own checker and simulator decide whether the result is a play worth staging. Nobody, human or model, cuts a slot that the stage cannot hold.
Small habits that help
- Ask for small edits ("add a transition", "extract a compound state") rather than "rewrite the chart". Small edits keep your layout and make diffs readable.
- Describe problems in the simulator's terms: the configuration, the enabled events, the event you sent and its payload. The agent can act on that exactly.
- Keep one chart per file, and give states ids that read as sentences. Both the canvas and the agent work better with
awaiting_paymentthan withs3. - Use
vt typecheckwith typed YAML charts: event payload types catch the mistakes a validator cannot. - Commit when the version changes, with the version IRI in the message. The history then reads as a list of behaviours.
Install the designer from the Vertepnyk page; the chart used here is fixtures/refund.scxml in its repository.