Drive-Thru Ordering Walkthrough
What You’ll Build
Section titled “What You’ll Build”A drive-thru ordering system where a fallible voice parser proposes what the customer said, the platform keeps those proposals behind candidate. relations, and the POS only receives accepted orders. The system enforces that absurd or low-confidence parses such as “18,000 waters” stay in review instead of becoming trusted facts or downstream actions.
This walkthrough is the cleanest demonstration of the candidate -> acceptance -> intent pattern:
voice.parse_result -> candidate.* -> accepted_order_* -> intent.*Taco Bell’s AI drive-thru went viral after it confidently submitted a customer’s joke order for 18,000 waters straight to the POS. The failure was not that the parser made a mistake — every sensor makes mistakes. The failure was that a mistake propagated directly into an action with no gate between transcription and execution. This example shows you the gate.
It covers the full JacqOS pipeline with a focus on fallible sensor boundaries:
- Observations arrive as JSON events (
order.started,voice.parse_result,customer.confirmation,crew.review,pos.result) - Mappers extract both trusted structural atoms and
requires_acceptancesemantic atoms from the same observation - Rules derive
candidate.*, current parse state, review gates, accepted order facts, and order status - Invariants enforce bounded acceptance and prevent successful POS submission without accepted order facts
- Intents derive outbound POS submission only from accepted order state
- Fixtures prove the happy path, correction-turn contradiction, impossible-order, and disagreement flows
Project Structure
Section titled “Project Structure”jacqos-drive-thru-ordering/ jacqos.toml ontology/ schema.dh # relation declarations rules.dh # candidate acceptance, review gates, invariants intents.dh # POS submission intent derivation mappings/ inbound.rhai # mapper contract + observation mapping fixtures/ happy-path.jsonl happy-path.expected.json contradiction-path.jsonl contradiction-path.expected.json impossible-order-path.jsonl impossible-order-path.expected.json disagreement-path.jsonl disagreement-path.expected.json prompts/ ordering-system.md # prompt bundle for package export generated/ ... # verification, graph, and export artifactsStep 1: Configure The App
Section titled “Step 1: Configure The App”jacqos.toml declares the app identity, the POS capability binding, and the Studio metadata:
app_id = "jacqos-drive-thru-ordering"app_version = "0.1.0"
[paths]ontology = ["ontology/*.dh"]mappings = ["mappings/*.rhai"]prompts = ["prompts/*.md"]fixtures = ["fixtures/*.jsonl"]
[capabilities]http_clients = ["pos_api"]models = []timers = falseblob_store = true
[capabilities.intents]"intent.submit_pos_order" = { capability = "http.fetch", resource = "pos_api" }This example does something important on purpose: JacqOS is not calling a live speech parser here. The fallible sensor output arrives as voice.parse_result observations, which means the walkthrough starts at the observation step while still demonstrating the same acceptance boundary. If you later decide to call a parser through an effect runtime, the ontology and mapper boundary can stay the same.
Step 2: Declare Relations
Section titled “Step 2: Declare Relations”The schema separates structural state, candidate evidence, accepted facts, review state, and effects:
relation order_started(order_id: text, lane_id: text)relation voice_parse_turn(order_id: text, parse_seq: int, confidence: float)
relation candidate.requested_item(order_id: text, item: text, parse_seq: int)relation candidate.quantity(order_id: text, quantity: int, parse_seq: int)relation candidate.modifier(order_id: text, modifier: text, parse_seq: int)
relation customer_confirmed(order_id: text, parse_seq: int)relation crew_rejected(order_id: text, parse_seq: int, reason: text)
relation accepted_order_item(order_id: text, item: text)relation accepted_quantity(order_id: text, quantity: int)relation accepted_modifier(order_id: text, modifier: text)
relation order_requires_confirmation(order_id: text)relation order_requires_review(order_id: text)relation order_status(order_id: text, status: text)
relation intent.submit_pos_order(order_id: text, item: text, quantity: int, modifier: text)The key point is that candidate.* and accepted_* are separate on purpose. The voice parser can propose combo meal x2, but until confirmation arrives, that is not the system’s accepted order.
Step 3: Map Observations To Atoms
Section titled “Step 3: Map Observations To Atoms”This example demonstrates mapper-level partial trust directly. The mapper contract marks only parse.* as requires_relay into candidate.*:
fn mapper_contract() { #{ requires_relay: [ #{ observation_class: "voice.parse_result", predicate_prefixes: ["parse."], relay_namespace: "candidate", } ], }}Then map_observation() extracts both ordinary and fallible atoms from the same voice-parse event:
if obs.kind == "voice.parse_result" { let atoms = [ atom("order.id", body.order_id), atom("turn.seq", body.seq), atom("turn.confidence", body.confidence), atom("parse.item", body.item), atom("parse.quantity", body.quantity), ];
if body.contains("modifier") { atoms.push(atom("parse.modifier", body.modifier)); }
return atoms;}That split is the whole design:
order.idandturn.seqare trusted structureparse.item,parse.quantity, andparse.modifierare fallible interpretation
Step 4: Derive Candidates, Review Gates, And Accepted Facts
Section titled “Step 4: Derive Candidates, Review Gates, And Accepted Facts”The first rules lift the parse into candidate evidence:
rule assert candidate.requested_item(order, item, seq) :- atom(obs, "order.id", order), atom(obs, "parse.item", item), atom(obs, "turn.seq", seq).
rule assert candidate.quantity(order, quantity, seq) :- atom(obs, "order.id", order), atom(obs, "parse.quantity", quantity), atom(obs, "turn.seq", seq).When a later parse arrives for the same order, the old candidates retract:
rule retract candidate.requested_item(order, item, old_seq) :- atom(obs, "order.id", order), atom(obs, "parse.item", item), atom(obs, "turn.seq", old_seq), voice_parse_turn(order, new_seq, _), old_seq < new_seq.That is how the contradiction path models a correction turn. The first parse remains visible as contradiction history, but it no longer drives current behavior.
Review gates sit between candidates and accepted facts:
rule order_requires_review(order) :- current_parse_seq(order, seq), candidate.quantity(order, quantity, seq), quantity > 8.
rule order_requires_review(order) :- current_parse_seq(order, seq), voice_parse_turn(order, seq, confidence), confidence < 0.7.Accepted facts derive only after confirmation and bounds checks:
rule accepted_order_item(order, item) :- current_parse_seq(order, seq), candidate.requested_item(order, item, seq), candidate.quantity(order, quantity, seq), quantity > 0, quantity <= 8, voice_parse_turn(order, seq, confidence), confidence >= 0.7, customer_confirmed(order, seq).This means the parser can say “18000 waters”, but the ontology still refuses to believe it.
Step 5: Derive POS Submission Only From Accepted Facts
Section titled “Step 5: Derive POS Submission Only From Accepted Facts”The outbound effect is intentionally narrow:
rule intent.submit_pos_order(order, item, quantity, modifier) :- accepted_order_item(order, item), accepted_quantity(order, quantity), accepted_modifier(order, modifier), not pos_submission_succeeded(order), not pos_submission_failed(order, _).There is no path from raw parse candidates to intent.submit_pos_order. That is the safety boundary in one rule.
Step 6: Fixtures
Section titled “Step 6: Fixtures”This example ships four fixtures:
Happy path
Section titled “Happy path”The parser says water x1, the customer confirms, and the order is submitted successfully. Final status: submitted.
Contradiction path
Section titled “Contradiction path”The first parse says burger x1. A correction turn replaces it with cola x2, no ice. The earlier candidates retract into contradiction history, and only the corrected order reaches POS.
Impossible-order path
Section titled “Impossible-order path”The parser proposes water x18000 at low confidence. The system derives order_requires_review and never derives accepted order facts or a POS submission intent. The original viral failure recreated as a fixture: the same parser output that crashed Taco Bell’s headlines lands here in Waiting, with the bound check and the missing confirmation both named in the drill inspector.
Disagreement path
Section titled “Disagreement path”The parser produces a plausible parse, but the crew rejects it after the customer clarifies the order. The candidate facts stay visible for audit, while the final order status becomes rejected. The crew rejection is itself an observation, so the entire transcript — what the parser said, what the crew overruled, why — is preserved in the timeline. There is no hidden state machine that quietly forgets the contested parse.
Step 7: Verify
Section titled “Step 7: Verify”Run all fixtures and invariants on a clean database:
$ jacqos verifyReplaying fixtures... happy-path.jsonl PASS (3 observations, 7 facts) contradiction-path.jsonl PASS (4 observations, 9 facts, 3 contradictions) impossible-order-path.jsonl PASS (1 observation, 4 facts) disagreement-path.jsonl PASS (3 observations, 6 facts)
Checking invariants... acceptance_within_quantity_bound PASS pos_submission_requires_accepted PASS
All checks passed.Every fixture replays from scratch deterministically — same observations, same evaluator, same facts every time. The contradiction count in contradiction-path.jsonl is expected: it reflects the candidates that were superseded by the correction turn, not an error.
What You’ll See In Studio
Section titled “What You’ll See In Studio”Open the demo with jacqos studio and the bundled happy-path fixture loads automatically. Switch fixtures from the timeline picker to walk every scenario:
- Normal order -> the
Donetab shows a row likeaccepted_order: water x1 — confirmed by customer, submitted to POS. Drill in and the inspector takes you from POS submission back throughaccepted_order_item, the customer confirmation, and the original voice-parse observation. - 18,000 waters -> the
Waitingtab shows a staged parse that never accepts. Drill in and the inspector surfaces theorder_requires_reviewrule, the missing confirmation, and the bounds check that would fail if the parse were promoted. The POS submission never derives. - Correction turn -> a second parse arrives for the same order; the first staged candidates retract and the second takes their place. Activity animates the transition and the contradiction history stays inspectable.
- Crew disagreement -> the
Blockedtab shows the rejected order with the specific crew-rejection observation that gated it.
At no point does JacqOS need to trust the parser. The parser can be as bad as you like. Containment does not depend on its quality.
Why This Example Matters
Section titled “Why This Example Matters”This is the fallible-sensor pattern in its purest form:
- the parse is visible
- the parse is queryable
- the parse can drive confirmation or review
- the parse cannot silently become trusted fact
- trusted fact alone can drive external action
That is how you stop a funny drive-thru glitch from turning into an operational outage.
Make It Yours
Section titled “Make It Yours”The drive-thru example is one shape of fallible-sensor containment. The same pattern fits:
- Vision tagging pipelines — a model proposes a label; an acceptance rule requires either confidence above a threshold or a human review signal before the label becomes accepted.
- OCR-driven document workflows — a model proposes extracted fields; an acceptance rule gates downstream filing on a reviewer signal or a cross-document consistency check.
- LLM extractors over patient or customer notes — see Medical Intake for the same pattern applied to clinical extraction.
- Sensor-fusion alarms — multiple noisy sensors propose state changes; an acceptance rule requires quorum across sensors before an alarm intent fires.
Any time you have a sensor whose output you cannot fully trust, the candidate-evidence pattern fits. To start building, scaffold a starter app with:
jacqos scaffold --pattern sensor my-sensor-appNext Steps
Section titled “Next Steps”- Fallible Sensor Containment — the pattern page that explains the
candidate.*namespace and acceptance-rule mechanics in depth - Using Fallible Sensors Safely — the canonical guide covering the value-first pattern, the mapper contract, the acceptance-rule reference, and why the boundary lives at the mapper and ontology layers
- Chevy Offer Containment Walkthrough — the symmetric
proposal.*containment example for fallible LLM deciders - Medical Intake Walkthrough — the same pattern applied to LLM extraction with clinician review
- Observation-First Thinking — the underlying evidence-first mental model