Skip to main content

Your first flow

Five verbs, in order, and yam alone tells you which one is next.

init → record (or write a flow, then bind it) → check → run → heal

1. Start a project

npm install --save-dev @svatah/yam
npx @svatah/yam init my-project
cd my-project
yam

yam init asks four things: the project's name, the adapter that drives the application, where the application runs locally, and whether it runs anywhere else. The last one is the project's endpoints: a local one, and any number of remote ones, each with a base URL and a kind (test, staging or production). Then it writes yam.config.yaml, a flows/ directory with one story that works against any application, and a data.yaml.

From a script, the same answers are flags, and nothing is asked:

yam init my-project --url http://localhost:3000 \
--endpoint staging=https://staging.example.com@staging \
--endpoint production=https://example.com@production

Every command then runs against the local endpoint until --endpoint <name> (or YAM_ENDPOINT=<name>) picks another: its base URL, storage state and kind replace app: and environment: for that command, and the policy sees the kind — a production endpoint refuses to record. yam with nothing after it prints where you are, which endpoint you are pointed at, and what to do next, and it does so at every step from here on:

my-project · /home/me/my-project
flows 1 file, 1 story
app http://localhost:3000 · test (also: staging, production)
plan missing
last run none yet

next yam check
There is no plan yet.

2. Record the flow, write it, or let an agent draft it

The quickest first flow is the one you do:

yam record

A browser opens at your application. Drive it: each click and each value you enter becomes a sentence of the flow, and each element you touch a binding. A password is never written down; the story declares a secret input and the sentence types it. Press Enter at the terminal when you are done, and the flow is under flows/<story>.flow, ready to check and run:

story: Sign in
inputs: password: secret
Go to "/login"
Type "someone@example.com" into the username field
Type {input.password} into the password field
Click the sign in button
The URL should contain "/dashboard"

test: Sign in

An agent can write the first draft instead: point any MCP host at yam explore as its server, let the agent drive the application saying what it is trying to do, and when it disconnects the exploration becomes a proposal under proposals/<date>/. yam then names it as the next thing to review; move its flow into flows/ when it says what you meant. Or write the flow yourself, and bind its targets afterwards (step 4):

story (tags=smoke): Sign in
inputs: username: string, password: secret
Go to "/login"
Type {input.username} into the username field
Type {input.password} into the password field
Click the sign in button
The dashboard heading should be visible

test: Sign in

A story is the unit of behaviour, a test block says which stories a run executes, and inputs: is a typed signature: a secret is redacted wherever it could be written. Every sentence pattern is in the flow language reference, and yam help flows shows the ten most used.

3. Check

yam check

One verb reads, lints and compiles the flows and writes .yam/plan.json. The grammar compiles each sentence deterministically; a sentence it cannot parse is refused with a suggestion. The plan records what it was compiled from, so run and record --flow notice when the flows changed and check again, saying so on one line.

4. Bind the targets of a flow you wrote

A flow you recorded is already bound; skip to step 5. A flow you wrote, or an agent drafted, names elements — the username field — that have no binding yet, and yam says so:

yam record --flow flows/sign-in.flow # or --all, for every unbound target

Yam drives the flow against your application, step by step. A browser opens and, at each target with no binding, an overlay names it and waits for your click; with a model credential configured a model grounds it instead and shows you its choice. Either way the recorder then writes bindings/<page>/<element>.yaml with its candidates and fingerprint. Nothing is written before you have seen it. yam now says next yam run.

5. Run

yam run

The plan is replayed and the exit code is the verdict: 0 passed, 1 failed, 6 passed only because a binding was healed. A failed step prints its reason and the verb that resolves it under the line. The run directory runs/<id>/ holds results, summary, audit, checkpoints and screenshots, and yam help exit-codes is the whole table.

6. Heal, when the interface moves

yam heal

With nothing after it, heal takes the last run, relocalizes every binding that stopped resolving against its recorded fingerprint, and proposes a diff for you to read. --apply writes the accepted repairs.

Next: one plan, three ways to run it. For the cockpit and the tmux workspace, yam ui and yam ui --tmux.