Tutorial · 2026

Build a minimum viable control plane, step by step

This tutorial takes one risky workflow in your AI product and gives it the eight artifacts that make its behavior governable: commitments, a semantic component, contracts for speech and action, an authority gate, compiled context, a renderer rule, an eval pack, and a replayable trace. Plan on a week: two days of decisions, three days of wiring.

You do not need a platform team, a vendor, or a rewrite. Every artifact is a small file in version control, and a runnable reference implementation exists so you can watch the whole loop work before you build your own. The examples below follow Corredor, the book's fictional remittance product; substitute your own workflow throughout.


One idea drives everything below: the product owns meaning; the model owns expression. Your model may write every sentence and draw every card, but what the product has promised, what it may do, what it treats as true, and how you would prove any of it later all live in artifacts you own, version, and test. When that boundary holds, a model upgrade is a configuration event. When it does not, every prompt is product logic nobody documented.

Why one workflow? Because abstraction costs something, the cost lands up front, and the payoff arrives at scale. Building the full architecture before one workflow has proven it is buying insurance against a fire that is not burning. Build the narrowest governed loop, watch it catch something, and let the evidence argue for the rest.

Step 1 · 1 hour

Pick one risky workflow

Two variables decide where to start: what a wrong sentence costs, and how many places it can appear. Look for the workflow where the answer to the first is "money, safety, rights, or compliance" and the answer to the second is "more than one channel or market." In Corredor it is the first-time transfer above the identity-verification threshold. In your product it might be a refund decision, a dosage explanation, a cancellation flow, a quote.

Write it down, one page, five headings:

# /experience-system/workflows/first_transfer.md

Intent:        send_money_today (first transfer, above threshold)
Consequence:   money movement, regulated disclosures, manual review
Channels:      WhatsApp first, mobile app, support dashboard
Owners:        product (flow), compliance (disclosures), eng (tools)
Failure modes: disclosure drift, softened requirements, premature
               confirmation, invented policy, skipped education
Check before you continueYou can say out loud what a wrong sentence in this workflow costs, and name the person who owns each failure mode. If nobody owns one, you have found your first real gap.

Go deeperThe Adoption Threshold gives the full decision table and the maturity ladder, including when the honest answer is "a prompt file is fine."

Step 2 · half a day

Write the commitments

Book a room with whoever owns risk for this workflow: legal, compliance, support, product. Leave with three lists. What the product must say before the user acts. What it must never imply, no matter how warm the phrasing. Which actions require approval before they run.

Be concrete to the point of discomfort. Not "be transparent about verification" but "say verification is required, disclose that it may delay the transfer, and never promise a completion time." The test for a commitment is that a validator could check it: a fact is something true about the world; a commitment binds the product and has an owner, a version, and a test.

Check before you continueEvery entry on all three lists has a named owner, and your compliance person has initialed the must/must-never lists. This half day is where most of the tutorial's value lives.

Go deeperThe Core Model explains why facts and commitments are different things and why the middle layer needs its own owner.

Step 3 · 1 hour

Define one semantic component

A semantic component is a reusable behavior, not a prompt: it declares its intent, the facts it may not run without, the actions it may offer, the commitments it must honor, and the evals that test it. Pick the single behavior at the heart of your workflow. For Corredor it is explaining identity verification:

# /experience-system/components/explain_identity_verification.yaml
id: component.explain_identity_verification.v1
intent: explain
requiredFacts:
  - verification_required_above_threshold
  - transfer_may_be_delayed
  - accepted_identity_documents
allowedActions:
  - upload_identity_document
  - contact_support
requiredCommitments:
  - disclose_required_status
  - disclose_delay_risk
contractId: contract.explain_identity_verification.v1
evalPackId: evalpack.explain_identity_verification.v1

The decision that matters: what happens when a required fact is missing because a policy service is down or stale? The component refuses and falls back to something safe, a fuller deterministic explanation or a human handoff. What it must never do is let the model improvise the missing fact.

Check before you continueThe component has one intent, is described without naming any channel, and every required fact traces to a source system you can name.

Go deeperSemantic Components covers the vocabulary (Explain, Confirm, Repair, and the rest), component boundaries, and versioned rollout.

Step 4 · half a day

Write the conversation contract

The contract turns step 2's lists into something enforceable. It names the verified facts in, the required claims, the forbidden claims, the output shape, and, critically, what happens when validation fails:

# /experience-system/contracts/conversation/explain_identity_verification.yaml
id: contract.explain_identity_verification.v1
facts:
  required: [verification_required_above_threshold,
             transfer_may_be_delayed, accepted_identity_documents]
commitments:
  must:     [say_verification_is_required, disclose_delay_risk,
             offer_next_step]
  mustNot:  [imply_verification_is_optional,
             promise_completion_time, invent_fee_or_policy]
outputShape:
  maxWords: 90
validators:
  - disclosure_required_status
  - discloses_delay_risk
  - no_completion_time_promise
degradeTemplate: >-
  To send more than $500 we need to verify your identity. It is
  required, and your transfer may be delayed while we complete it.
  You can upload your ID here, or contact support for help.

Notice the model is being asked to express, not to decide. And decide the failure behavior now: there are only four choices when a validator fires: retry narrower, degrade to the approved template, block and hand to a human, or block the action while the conversation continues. The boring defaults are right: required disclosures degrade to templates; consequential actions block. A stiff but accurate sentence beats a fluent omission.

Check before you continueA colleague who has never seen this workflow can read the contract and tell you what the model may never say. The degrade template exists and compliance has approved its wording.

Go deeperContracts, Capabilities, and Authority covers contract boundaries and the full validator-failure decision.

Step 5 · 2 hours

Bound the action

Your workflow ends in something with a side effect: a transfer, a refund, a booking, a cancellation. That action gets a contract of its own, because the expensive failure mode is not what the product says but what it does:

# /experience-system/contracts/actions/initiate_transfer.yaml
id: action.initiate_transfer.v1
tool: transfer_api.create_transfer
sideEffect: creates_pending_money_transfer
requiredInputs: [senderId, recipientId, amount, quoteId]
preconditions:
  - sender_authenticated
  - recipient_confirmed
  - quote_not_expired
  - disclosure_acknowledged
humanApprovalWhen:
  - amount_over_1000
reversibility:
  cancel_before_status: submitted_to_partner
audit: [contract_version, tool_input_hash,
        user_confirmation_event_id]

The Chevrolet lesson applies here: an assistant that can discuss a sale is one missing check away from appearing to make one. The preconditions are the difference between "the model said yes" and "the product committed."

Check before you continueEvery precondition is checkable from system state, not from conversation text. "The user seemed sure" is not a precondition.
Step 6 · 1 hour

Decide authority

Contracts define boundaries; authority defines who may cross them, and when. Without an authority gate, every tool wired into your system is implicitly available to every user, on every channel, at every amount. That is never what anyone intended, and it is exactly what the model will assume:

# /experience-system/authority/transfer_execution.yaml
id: authority.transfer_execution.v1
subject: user.sender
capability: transfer.initiate
allowedWhen:
  - sender_authenticated
  - recipient_confirmed
  - quote_not_expired
  - disclosure_acknowledged
humanApprovalWhen:
  - amount_usd > 1000
replayEvidence: [authority_version, policy_decision_id,
                 user_confirmation_event_id]

Run this gate before anything renders, so the interface never offers an action the system already knows it cannot honor. In the reference implementation, the confirm button arrives disabled because authority said so, not because the model chose restraint.

Check before you continueYou can answer, from the artifact alone: who may trigger the action, under what evidence, and what happens above the approval threshold.
Step 7 · half a day

Compile context with provenance

The NYC MyCity bot told businesses to break the law because it had ingested a mountain of content with no layer that could say which facts were current, which were superseded, and which it had no authority to state. Your repair is a context compiler: instead of dumping everything into the model, gather candidate facts, check where each came from, resolve contradictions, and emit a bounded packet:

{
  "context_packet_id": "ctx_4471",
  "task_id": "task_transfer_4471",
  "facts": [
    { "key": "verification_required", "value": true,
      "source": "policy.identity.v12",
      "authority": "compliance_approved", "freshness": "2026-07-01" },
    { "key": "transfer_literacy", "value": "first_time_sender",
      "source": "memory.user.transfer_literacy", "confidence": 0.78 }
  ],
  "excluded": [
    { "key": "old_delay_policy",
      "reason": "superseded_by_policy.identity.v12" }
  ],
  "safe_default": "render_required_disclosures"
}

Three rules keep it honest. Declare precedence: compliance-approved beats cached, newer beats older. Fail closed: when the compiler cannot decide, run with every required disclosure and no personalization. And give it a latency budget from day one: compile once per task, cache the packet, invalidate on source change. A compiler that takes eight hundred milliseconds per message will be quietly bypassed by the first engineer under deadline pressure.

Check before you continueFor any fact the model can state, you can point at its source, its freshness, and the rule that admitted it. The packet records what was excluded and why.

Go deeperContext Compiler, Memory, and Knowledge adds memory write policies: purpose, retention, consent, and correction.

Step 8 · 1 hour

Constrain the renderer

A disclosure that is technically present but folded below a detail section has not been disclosed. Renderer contracts are salience rules: what must be visible, in what order, before which affordance:

# /experience-system/contracts/renderers/whatsapp_text.yaml
id: renderer.whatsapp_text.v1
salience:
  disclose_delay_risk: visible_before_confirm
  fee_and_rate: visible_before_confirm
actions:
  confirm_transfer: enabled_only_when_authorized
forbidden:
  - hide_required_disclosure

Test these the way you test accessibility, because they are the same kind of rule: is the disclosure inside the first viewport, is it before the confirm control in reading order, is the disabled button actually disabled rather than styled gray? And define the fallback: when a generated layout fails its checks, render the plain, pre-approved version. It will be ugly. It will also be correct.

Check before you continueThe salience rules are written down per channel, and there is a deterministic fallback layout that satisfies all of them.
Step 9 · half a day

Ship the eval pack

The eval pack travels with the component and gates its releases. Fixtures are realistic task states; graders are the validators from step 4; the release gate blocks on required failures:

# /experience-system/eval-packs/explain_identity_verification.yaml
id: evalpack.explain_identity_verification.v1
artifact: component.explain_identity_verification.v1
fixtures:
  - name: first_time_sender_high_value
    context: { amount_usd: 900, locale: es-MX,
               transfer_literacy: first_time_sender }
  - name: returning_sender_high_value
    context: { amount_usd: 900, locale: es-MX,
               transfer_literacy: understands_standard_transfer }
graders:
  required:
    - disclosure_required_status
    - discloses_delay_risk
    - no_completion_time_promise
releaseGate:
  blockOnRequiredFailure: true

The unit of evaluation is the behavior the person experienced, not the model response. When a contract changes, its eval pack changes in the same review. When a model upgrade arrives, this pack is what decides whether it ships, which is what makes the model replaceable and the commitments not.

Check before you continueRunning the pack against your current model passes every required grader, and deliberately breaking the model (try a system prompt that says "be extra reassuring") makes it fail. An eval pack that cannot fail is a decoration.

Go deeperEvaluation-Driven Experience Design covers online evaluators, human review, and turning production failures into fixtures.

Step 10 · 2 hours

Emit a replayable trace

When something goes wrong, the team that argues from screenshots loses a week; the team that replays the trace repairs the responsible artifact in an afternoon. One record per turn, connecting everything that decided it:

{
  "trace_id": "trc_8f3a",
  "task_id": "task_transfer_4471",
  "intent": "send_money_today",
  "context_packet_id": "ctx_4471",
  "authority": { "id": "authority.transfer_execution.v1",
                 "allowed": false,
                 "failed": ["recipient_confirmed"] },
  "policies": ["policy.identity.v12"],
  "graph_id": "graph.first_transfer_confirmation.v1",
  "planner": { "decision": "keep_full_graph", "reversible": true },
  "contracts": ["contract.explain_identity_verification.v1"],
  "renderer": "whatsapp_text",
  "model": "provider/model/version",
  "validators_failed": [],
  "degraded": false,
  "outcome": "verification_started"
}

Generic telemetry says the system ran. Domain evidence says whether the product kept its word: which disclosure was required, which policy version applied, which authority gate ran, what the person actually saw. Traces are product records, not debug logs: give them stable ids, retention rules, and privacy boundaries.

Check before you continueFrom one trace id, someone who was not there can reconstruct what was admitted, permitted, said, shown, and done, and name the version of every artifact involved.
Step 11 · 1 hour

Run it end to end

Before wiring your own product, watch the loop work. The book's reference implementation is the whole tutorial in about four hundred lines:

git clone https://github.com/kyle-c/corredor-control-plane
cd corredor-control-plane
npm install
npm run demo    # happy path, a misbehaving model caught by
                # validators, and bounded planner adaptation
npm run evals   # the eval pack, with a real release gate

The demo's second scenario is the one to internalize: an "overly warm" expression engine promises same-day delivery, the validators catch it, and the output degrades to the approved template while the trace records what happened. That is your architecture working. The expression engine is a deterministic template on purpose: swap in any model and every gate holds identically.

Then wire your own artifacts into your stack. The runtime is not exotic: compile context, check authority and policy, run the contract, validate, render, trace. If your product already has a request pipeline, the control plane is middleware, not a rewrite.

Check before you continueYour workflow runs end to end in a staging environment: the disclosure renders before the confirm, the action blocks without authority, a forced validator failure degrades to the template, and every turn writes a trace.

Go deeperReference Implementation Patterns covers compiling prompts from artifacts and the patterns that should be non-negotiable.

Step 12 · 30 minutes, weekly

Measure, then earn the second workflow

Everything so far was construction. This step is the habit that justifies it. Weekly, thirty minutes:

  1. Read the validator firings. A validator that fires constantly means the contract is wrong or the model needs a narrower task; the traces tell you which.
  2. Promote the week's worst production trace into a new eval fixture.
  3. Check incidents and time-to-repair against the pre-control-plane baseline. These two numbers are the argument for the second workflow.
  4. Ask the sunset question: is any layer only compensating for model weakness? Model progress should keep simplifying the implementation. The obligations stay.

When a model upgrade lands, the routine is anticlimactic by design: run the eval packs, read the diff in behavior, ship or hold. The upgrade is allowed to improve expression. It must pass the same commitments.

Go deeperGovernance, Ownership, and Auditability maps this rhythm onto NIST, ISO 42001, and EU AI Act expectations. Corredor With Scars shows a full incident and its trace-driven repair.


When it goes sideways

Four problems account for most stuck moments. The two-line fix for each.

  • A validator fires constantly. Either the contract asks for the impossible or the task is too broad. Read ten firing traces; narrow the component's intent or fix the contract. Do not silence the validator.
  • The context compiler is too slow. Compile once per task, not per message. Cache the packet; invalidate on source version change. If it is still slow, your sources are the problem, and that is worth knowing.
  • Teams route around review. Your review gates are disproportionate. Copy-only changes need content review, not compliance review. Re-tier the gates before the workaround culture sets in.
  • The degrade template ships too often. That is the system telling you the model cannot yet hold this contract. Narrow the task, add worked examples to the tool descriptions, or accept the template: it is accurate, and accuracy was the point.

Where this tutorial ends and the book begins

The tutorial builds the narrowest governed loop. The book is the argument, the failure modes, and the judgment. The map:

Tutorial stepBook chapterWhat the book adds
1. WorkflowThe Adoption ThresholdThe full decision table, maturity ladder, and sunset conditions
2. CommitmentsThe Core ModelWhy facts and commitments differ; the three layers and their owners
3. ComponentSemantic ComponentsThe intent vocabulary; boundaries; versioned rollout
4–6. Contracts & authorityContracts, Capabilities, and AuthorityTool contracts as interfaces; the boundary table; the Chevrolet incident in full
7. ContextContext, Memory, KnowledgeMemory write policies; the MyCity incident; compiler failure modes
8. RendererAdaptive Rendering and Generative UIGenerative UI as a contract-bound renderer, not a rival
9. EvalsEvaluation-Driven Experience DesignOnline evaluators; outcome metrics beyond contract checks
10. TracesRuntime, Corredor With ScarsThe bounded planner; a complete incident replay
11. Run itReference Implementation PatternsCompiled prompts; policy beside execution; the non-negotiables
12. MeasureGovernance, The Case Against the FrameworkRegulatory mapping; the objections, answered honestly

Read the book for the argument. Send the 13-slide brief to the stakeholder who needs convincing. And govern one workflow this month. The tutorial only works if you do.