walkthrough
This guide follows one project from start to finish. We write a plan for improving a checkout page, let an AI agent work through it, and keep the final launch decision with a person. Along the way, you will see how Marionette catches missing steps and keeps a record of every decision.
You do not need to know Marionette's terminology before you begin. Each new idea is explained when it appears. The terminal examples are recordings of the real command-line tool, but you can understand the guide without reading every line of output.
1 · Describe the project
Our goal is to improve an online checkout. The team will test changes with a small group of customers, try again if the results are poor, and ask a person to approve the full launch. The plan also needs an answer for what to do if repeated changes do not help.
A Marionette plan is a plain-text file. Each section is a stage of the project, and each choice says where to go next. Here is the first draft. It has two mistakes: the retry path has not been marked as intentional, and the rollout stage has no next step.
VAR attempts = 0
=== build_checkout ===
Rebuild the checkout flow behind a feature flag.
Ship one measurable change per attempt to the flag cohort and read the
conversion funnel after a full week; an attempt is done when its data is in.
~ attempts += 1
* [Cohort converts] @human -> rollout
+ [Conversion flat — iterate] -> build_checkout
=== rollout ===
Ramp the flag to 100% and retire the old flow.
Watch error rates and conversion during the ramp; done means the old flow is
deleted, not merely dark.2 · Check the plan for missing paths
The validate command checks whether every stage can be
reached and whether every path eventually goes somewhere. It reports both
mistakes and points to the lines that need attention:
$ marionette validate checkout.marcheckout.mar:19: error[MAR006]: phase "rollout" is a dead end: no choices and no divert 19 | === rollout === help: add a choice or divert (e.g. "-> END") so every path has an exitcheckout.mar:17: error[MAR008]: undeclared cycle: build_checkout -> build_checkout 17 | + [Conversion flat — iterate] -> build_checkout help: cycles must be intentional: mark the returning choice with ~loop~✗ checkout.mar: 2 errors, 0 warnings
The first error asks whether returning to an earlier stage is deliberate and, if so, when the retries should stop. The second asks what happens after rollout. Marionette will not give an incomplete plan to an agent.
3 · Fill in the missing decisions
We decide that the team may try up to five versions. The
~loop~ marker says that returning to the build stage is
intentional, while attempts < 5 sets the limit. If all
five attempts fail, the plan moves to rethink and asks a
person what to do. @human marks a choice that an AI agent is
not allowed to make. Finally, a successful rollout now leads to
END, which means the project is complete.
VAR attempts = 0
=== build_checkout ===
Rebuild the checkout flow behind a feature flag.
Ship one measurable change per attempt to the flag cohort and read the
conversion funnel after a full week; an attempt is done when its data is in.
~ attempts += 1
* [Cohort converts] @human -> rollout
+ {attempts < 5} [Conversion flat — iterate] ~loop~ -> build_checkout
* {attempts >= 5} [Not converging — rethink] @human -> rethink
=== rethink ===
Five iterations without lift: take the flow back to research.
Run the checkout usability study and write up why the five attempts failed;
that write-up is the input to whichever door is taken next.
+ [New direction agreed] @human ~loop~ -> build_checkout
* [Park the revamp] @human -> END
=== rollout ===
Ramp the flag to 100% and retire the old flow.
Watch error rates and conversion during the ramp; done means the old flow is
deleted, not merely dark.
-> END$ marionette validate checkout.mar --strict✓ checkout.mar: 0 errors, 0 warnings
4 · Prepare the plan for an agent
The compile command turns the text file into JSON: a
structured version that software can read reliably. Marionette also gives
this version a unique fingerprint. Later, it uses that fingerprint to
notice if someone changes the plan while work is under way.
$ marionette compile checkout.marwrote checkout.trajectory.json✓ compiled 3 phases, 5 choices (sha256:24d32e0b2820…)
5 · Review the plan without learning the syntax
Reviewers do not have to read the plan file. The
summarize command explains the route in plain language and
highlights every decision reserved for a person. The
render command creates a flowchart. In that chart,
✋ marks a human decision and ↻ marks a retry
path.
$ marionette summarize checkout.mar# Plan summary: checkout.mar**Intent:** Lift checkout conversion behind a feature flag — up to five evidence-driven iterations, a human ship gate, and a forced rethink when the budget runs dry.> Checkout conversion has been flat for two quarters. Rebuild the flow> behind a flag and iterate on cohort evidence — five attempts max. I take> the ship call, and if it never converts we go back to research rather> than grinding out attempt six.Starts at **build_checkout**. 3 phases, 2 decision points (phases with 2+ choices), 5 choices overall.- **Human checkpoints:** "Cohort converts" (at build_checkout); "Not converging — rethink" (at build_checkout); "New direction agreed" (at rethink); "Park the revamp" (at rethink)- **Declared loops:** build_checkout → build_checkout ("Conversion flat — iterate"); rethink → build_checkout ("New direction agreed")- **Gates:** 2 gated choices, of which 0 unverified (review manually)- **Variables:** attempts: number = 0- **Contract hash:** `sha256:24d32e0b2820…`## Walkthrough### build_checkout (start)Rebuild the checkout flow behind a feature flag.Ship one measurable change per attempt to the flag cohort and read theconversion funnel after a full week; an attempt is done when its data is in.- on entry: `attempts += 1`- **Cohort converts** → rollout (**requires a human decision**)- **Conversion flat — iterate** → build_checkout (only if `attempts < 5`; loops back; repeatable)- **Not converging — rethink** → rethink (only if `attempts >= 5`; **requires a human decision**)### rethinkFive iterations without lift: take the flow back to research.Run the checkout usability study and write up why the five attempts failed;that write-up is the input to whichever door is taken next.- **New direction agreed** → build_checkout (**requires a human decision**; loops back; repeatable)- **Park the revamp** → END (**requires a human decision**)### rolloutRamp the flag to 100% and retire the old flow.Watch error rates and conversion during the ramp; done means the old flow isdeleted, not merely dark.- otherwise → END## Compiler reportNo defects, no warnings. The plan is structurally sound: every phase is reachable, every path has an exit, and all declared loops have a verified exit.
$ marionette render checkout.marflowchart TD build_checkout["<b>build_checkout</b><br/>Rebuild the checkout flow behind a feature flag."] rethink["<b>rethink</b><br/>Five iterations without lift: take the flow back to resea…"] rollout["<b>rollout</b><br/>Ramp the flag to 100% and retire the old flow."] build_checkout -- "✋ Cohort converts" --> rollout build_checkout -. "↻ Conversion flat — iterate {attempts < 5}" .-> build_checkout build_checkout -- "✋ Not converging — rethink {attempts >= 5}" --> rethink rethink -. "✋ ↻ New direction agreed" .-> build_checkout rethink -- "✋ Park the revamp" --> END rollout --> END END(((END))) classDef taken fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px; classDef current fill:#fff59d,stroke:#f57f17,stroke-width:3px; classDef frontier stroke:#1565c0,stroke-width:2px,stroke-dasharray:4 2; linkStyle 0 stroke:#c62828,stroke-width:3px; linkStyle 2 stroke:#c62828,stroke-width:3px; linkStyle 3 stroke:#c62828,stroke-width:3px; linkStyle 4 stroke:#c62828,stroke-width:3px;
6 · Start the work
state init starts a run and creates
checkout.state.json, a small file that records progress. The
run begins in the first stage, build_checkout. Its attempt
counter changes to 1, and Marionette shows which choices are available and
which are currently blocked.
$ marionette state init checkout.marinitialised checkout.state.json bound to sha256:24d32e0b2820…current: build_checkoutRebuild the checkout flow behind a feature flag.Ship one measurable change per attempt to the flag cohort and read theconversion funnel after a full week; an attempt is done when its data is in.variables: attempts=1choices: [0] Cohort converts @human -> rollout [1] Conversion flat — iterate ~loop~ {attempts < 5} -> build_checkout [2] Not converging — rethink @human {attempts >= 5} -> rethink [unavailable: gate {attempts >= 5} is false]
The agent does not need to inspect that file. It runs
brief to get its next assignment. The brief contains the
current instructions, useful values such as the attempt count, the choices
available now, and the command to record the result.
$ marionette brief checkout.marwork packet — checkout-revamp (sha256:24d32e0b2820…)status: active progress: 1/3 phases, 1 stepsintent: Lift checkout conversion behind a feature flag — up to five evidence-driven iterations, a human ship gate, and a forced rethink when the budget runs dry.prompt: Checkout conversion has been flat for two quarters. Rebuild the flow behind a flag and iterate on cohort evidence — five attempts max. I take the ship call, and if it never converts we go back to research rather than grinding out attempt six.=== build_checkout ===Rebuild the checkout flow behind a feature flag.Ship one measurable change per attempt to the flag cohort and read theconversion funnel after a full week; an attempt is done when its data is in.delivery: none · report per-phasevariables: attempts=1choices: [0] Cohort converts @human -> rollout — Ramp the flag to 100% and retire the old flow. [1] Conversion flat — iterate ~loop~ {attempts < 5} -> build_checkout — Rebuild the checkout flow behind a feature flag. [2] Not converging — rethink @human {attempts >= 5} -> rethink — Five iterations without lift: take the flow back to research. [unavailable: gate {attempts >= 5} is false]
7 · Keep the launch decision with a person
Choice [0] would approve the launch, and it is marked
@human. When the agent tries to take it, Marionette refuses.
The error is named human-checkpoint, and no progress is
recorded. This rule is enforced by the tool itself; it does not depend on
the agent remembering an instruction.
$ marionette state choose checkout.mar 0 --actor agent --rationale "metrics look good"error: choice "Cohort converts" is an @human checkpoint: an agent may not take it autonomously. Escalate to a human; a human records the decision with --actor <name>.
The agent can choose to try another version instead. It records why, returns to the build stage, and the attempt counter changes to 2. Anyone checking the project can now see both the decision and the remaining number of tries.
$ marionette state choose checkout.mar 1 --actor agent --rationale "conversion flat; iterating on the payment step"current: build_checkoutRebuild the checkout flow behind a feature flag.Ship one measurable change per attempt to the flag cohort and read theconversion funnel after a full week; an attempt is done when its data is in.variables: attempts=2choices: [0] Cohort converts @human -> rollout [1] Conversion flat — iterate ~loop~ {attempts < 5} -> build_checkout [2] Not converging — rethink @human {attempts >= 5} -> rethink [unavailable: gate {attempts >= 5} is false]
$ marionette state show checkout.marcurrent: build_checkoutRebuild the checkout flow behind a feature flag.Ship one measurable change per attempt to the flag cohort and read theconversion funnel after a full week; an attempt is done when its data is in.variables: attempts=2choices: [0] Cohort converts @human -> rollout [1] Conversion flat — iterate ~loop~ {attempts < 5} -> build_checkout [2] Not converging — rethink @human {attempts >= 5} -> rethink [unavailable: gate {attempts >= 5} is false]
8 · A person approves the launch
The test results show a 9% improvement. A person now takes the launch
choice and records the reason. Marionette keeps their name, the decision,
and the evidence in the project's history. After rollout, the plan reaches
END.
$ marionette state choose checkout.mar 0 --actor lee --rationale "cohort shows +9% completion; ship it"current: rolloutRamp the flag to 100% and retire the old flow.Watch error rates and conversion during the ramp; done means the old flow isdeleted, not merely dark.variables: attempts=2no choices; divert available -> END (marionette state advance)
$ marionette state advance checkout.mar --actor agent --rationale "flag at 100%, old flow retired"current: END (completed)variables: attempts=2
$ marionette brief checkout.marwork packet — checkout-revamp (sha256:24d32e0b2820…)status: completed ✓ progress: 2/3 phases, 4 stepsintent: Lift checkout conversion behind a feature flag — up to five evidence-driven iterations, a human ship gate, and a forced rethink when the budget runs dry.prompt: Checkout conversion has been flat for two quarters. Rebuild the flow behind a flag and iterate on cohort evidence — five attempts max. I take the ship call, and if it never converts we go back to research rather than grinding out attempt six.variables: attempts=2the plan has reached END — nothing left to execute.
9 · Safely change a plan that is already running
Imagine instead that the project is still on its second attempt and you
edit the plan to add a postmortem stage after rollout.
Marionette notices that the plan's fingerprint has changed. It stops with
exit code 3 rather than applying the old progress record to a different
plan. This mismatch is called drift.
$ marionette state show checkout.marplan/state drift detected: the script changed since this state was recorded. state is bound to sha256:24d32e0b28205dbad62004391795fe41fc4bfbb40696d30f6e7a62497635efa0 compiled plan is sha256:241d3a5771722af98cd25c8ce17df13f05a974c8be1f6a403a6205fa6d6ee710Reconcile before continuing: review the plan changes, then either re-initialise the state (marionette state init --force) or restore the previous script version.
If the edit is intentional, run state rebind. This updates
the progress file to use the new plan, keeps the existing decision
history, and lists what changed. You can review that list before work
continues.
$ marionette state rebind checkout.mar✓ migrated checkout.state.json from sha256:24d32e0b2820… to sha256:241d3a577172…current: build_checkoutRebuild the checkout flow behind a feature flag.Ship one measurable change per attempt to the flag cohort and read theconversion funnel after a full week; an attempt is done when its data is in.variables: attempts=2choices: [0] Cohort converts @human -> rollout [1] Conversion flat — iterate ~loop~ {attempts < 5} -> build_checkout [2] Not converging — rethink @human {attempts >= 5} -> rethink [unavailable: gate {attempts >= 5} is false]
What you have done
You wrote a project plan, checked it for missing paths, gave an agent only the decisions it was allowed to make, and kept a clear history of the work. You also changed a running plan without losing that history.
Next, follow getting started to try this with your own plan. If you are connecting Marionette to another tool, the execution guide explains the machine-readable interface.