datumwise

Cascadia Retail is a fictional 24-store chain with an ordinary warehouse and two famous bad numbers. This case walks the whole arc — the company, the data, the ask, the design and its reasons, and the system live — and every number in it is recorded, never illustrated.

Cascadia Retail — the setup

Chapter 1 of the case demo: the company, the data they have, and what they asked for. Nothing here is about Columna yet — this is the situation any solution would face.


The company

Cascadia Retail runs 24 stores across four regions (north, south, east, west), selling about 600 products to a customer base of roughly 10,000 registered accounts. A small data team maintains a warehouse; a Sales Operations team lives in it.

The data they have

The warehouse is ordinary — which is to say, it has history. Fourteen tables:

The facts:

  • transactions — one row per sale line: customer, store, product, day, amount, units, units returned, a satisfaction score (often null), and the customer's region copied onto the row. ~20,000 rows over two years.
  • eom_inventory — daily stock snapshots per store: store, day, level. ~17,000 rows.

The reference tables:

  • stores (24 — with region and opened_date), customers (~10,000 — with segment, region, signup date), products (600 — with base price), categories (12), and product_categories — a bridge, because a product can sit in up to three categories.
  • calendar — two years of days mapped to week, month, quarter, year.

The rest — every warehouse has these:

  • Four pre-built summary tables (daily_revenue_summary, monthly_avg_order_value, monthly_unique_visitors, monthly_store_inventory) that someone made for a dashboard years ago. Convenient, and nobody is sure they're still right.
  • Two side tables from other teams (support_tickets, engagement_scores — the latter covering only about half the customers).
  • warehouse_notes — three rows of tribal knowledge, including: "summary tables are convenience copies; the base tables are authoritative."

The demo shows each table's schema and a few real rows before anything else happens — if you don't know the data, nothing downstream can make sense.

generated Fourteen tables, straight from the bundled warehouse — the six that drive the story expanded, the rest in full (note daily_revenue_summary's 716 rows against the calendar's 731):

transactions 19,995 rows
txn_idcustomer_idstore_idproduct_iddayamountunitsunits_returnedsatisfactioncustomer_region
T00000015C003262S001P05122024-01-0358.261None5.7north
T00000030C016296S001P00222024-01-05218.18120.08.2west
T00000045C010086S001P05112024-01-0835.5310.0Noneeast
eom_inventory 16,839 rows
store_iddaylevel
S0012024-01-015693.0
S0012024-01-025630.0
S0012024-01-035622.0
stores 24 rows
store_idnameregionopened_date
S001Store 001east2021-09-22
S002Store 002south2021-09-22
S003Store 003east2021-09-22
calendar 731 rows
dayweekmonthquarteryear
2024-01-012024-W012024-012024Q12024
2024-01-022024-W012024-012024Q12024
2024-01-032024-W012024-012024Q12024
product_categories 870 rows
product_idcategory_id
P0001G07
P0001G12
P0002G09
daily_revenue_summary 716 rows
dayrevenue
2024-01-012022.91
2024-01-021671.58
2024-01-032538.42

The other eight — references and the stale summaries the model won't use:

customers 10,157 rows customer_id, name, segment, region, signup_date
customer_idnamesegmentregionsignup_date
C000001Customer 000001premiumnorth2024-05-12
C000006Customer 000006businessnorth2024-06-03
C000007Customer 000007consumerwest2024-09-11
products 600 rows product_id, name, base_price
product_idnamebase_price
P0001Product 000124.48
P0002Product 000250.74
P0003Product 000328.11
categories 12 rows category_id, name
category_idname
G01Category A
G02Category B
G03Category C
monthly_avg_order_value 24 rows month, avg_order_value, n_txns
monthavg_order_valuen_txns
2024-01139.91619
2024-02125.81591
2024-03127.25648
monthly_unique_visitors 24 rows month, unique_visitors
monthunique_visitors
2024-01617
2024-02589
2024-03646
monthly_store_inventory 554 rows store_id, month, total_inventory
store_idmonthtotal_inventory
S0012024-01179656.0
S0012024-02159713.0
S0012024-03171408.0
support_tickets 500 rows ticket_id, customer_id, day, topic
ticket_idcustomer_iddaytopic
T00001C0090032024-02-17return
T00002C0056912024-03-10account
T00003C0091772024-12-29shipping
engagement_scores 5,028 rows customer_id, score
customer_idscore
C0000010.771
C0000070.989
C0000090.321

What Sales Ops asked for

Dana runs Sales Operations. Her team answers questions for regional managers all week, by hand, in SQL and spreadsheets. Her request, lightly edited:

"We want the team — and honestly, the managers themselves — to get answers from the AI assistant we already use, without waiting on us. But it has to be right, and it has to say what it's assuming. We've been burned."

The questions her team answers every week (this list is the requirement):

  1. Revenue and order count — by store, by region, by month, by quarter.
  2. Average order value, by month — and by store when a manager asks.
  3. Best- and worst-performing products this month; top stores per region.
  4. Return rate (units returned over units sold), by product and by month.
  5. End-of-month inventory position, per store.
  6. Unique customers who bought, by month and by region.

The two times they got burned (this is the trust bar):

  • The inventory total. A deck went to the exec team with "total inventory by month" — daily snapshots summed across the month. The number was meaningless (thirty days of the same stock, added together), and nobody caught it until a warehouse manager laughed at it.
  • The two revenues. Two decks in the same meeting showed different monthly revenue. One had used daily_revenue_summary; the other computed from transactions. They disagree — the summary is missing days — and it took an afternoon to find out which was right.

Who has to be served — and this matters as much as the questions:

Dana's team spans an analyst with eight years of context — she knows the summary tables are stale, knows what level means, knows store S014 opened mid-year — and new hires who know retail generally but none of this warehouse's folklore. Both must get right answers: the veteran asking terse questions in her own shorthand, the new hire asking vague ones without knowing what to ask. "If the right answer depends on knowing our folklore," Dana says, "then the folklore has to live in the system, not in Priya's head."

Her constraints:

  • The assistant never gets raw table access. ("It will do what our interns did, faster.")
  • Every answer states its basis — what was counted, over what population, with what caveats.
  • If a question is ambiguous, it should come back as a question — not as a guess.
  • If something can't be answered honestly, say so and say why.

What "done" looks like

A regional manager types "how did the west do last quarter?" into the assistant and gets a right answer with its assumptions attached. A new hire in week one gets the same right answer the eight-year veteran gets — and when their question is too vague, the assistant asks back instead of guessing. Dana's team stops being a query service. And the two failure modes above become impossible — not discouraged, impossible: the meaningless inventory total can't be produced without a warning, and the stale summary table isn't in the answering path at all.

Next chapter: how the data team scoped and designed the Manifold to meet this — the decisions, the naming, and what they deliberately left out.


Cascadia Retail — the solutioning

Chapter 2 of the case demo: how the data team went from Dana's request to a Manifold design. The decisions, the reasons, and what was deliberately left out.


Working backward from the questions

The team started from Dana's six questions and asked, for each: what data answers it, at what grain, along what paths? That trace decided almost everything:

Dana's question What it needs
1. Revenue & orders by store/region/month/quarter transaction facts; store, region, and calendar grains; a path from store to region and from day to the calendar
2. Average order value revenue ÷ orders — a defined ratio, one definition for everyone
3. Best/worst products; top stores per region the product grain; ordered, top-n answers
4. Return rate by product and month units sold and units returned, summed; their ratio
5. End-of-month inventory position per store the stock snapshots — position, not totals
6. Unique customers who bought a distinct count of customers, by month and region

Two tables answer all six: transactions and eom_inventory. Everything else is either a reference table serving the grains and paths, or out of scope.

What we verified before designing

A design is only as good as what you actually know about the data. The team checked; each finding became a decision:

  • eom_inventory is misnamed — the data says daily. The name says end-of-month; the rows are one snapshot per store per day (~17,000 rows over two years). Almost certainly a fossil from when the feed was monthly. Decision: grain comes from the data, never from the name — the model treats it as the daily snapshot table it is.
  • transactions is a daily fact, and that's fine. Day grain, no timestamps — a daily rollup of an upstream transaction log the warehouse doesn't keep. A fact table summarizing a log is normal; the model just has to be honest that day is the finest time grain anyone can ask at.
  • The two facts have different grains. A transaction row is one sale line (customer × store × product × day); an inventory row is one snapshot (store × day). Different populations → they will be modeled as two separate populations, never silently mixed.
  • The snapshots are a running level, not a flow. Summing them across days produces Dana's meaningless number. Decision: summing stock across time will be blocked by declaration — the first burn becomes impossible, not discouraged.
  • The summary tables are stale. daily_revenue_summary has 716 rows against 731 calendar days — fifteen days missing. That's the second burn, explained. Decision: summary tables are not sources. The base tables are authoritative (the warehouse's own notes agree). They stay in the warehouse; they don't enter the model.
  • Weeks don't nest in months. The team checked the calendar: ISO weeks straddle month boundaries. So the calendar rollup chain is day → month → quarter → year, and week hangs as its own path off day — it can't sit in the chain.
  • A product can be in up to three categories. Any revenue-by-category rollup would triple-count. No question asks for it — but someone will. Decision: declare the relationship as explicitly many-to-many, so when the question comes, the refusal can name exactly why.
  • transactions.customer_region is a denormalized copy. "By region" in Dana's questions means store geography (managers own stores). Decision: one region concept, reached from store via the store table; the copied column is ignored.
  • units_returned is null on early rows — returns tracking started partway through the history. Decision: nulls count as no-return, and the return-rate definition says so in its description (folklore into the system).
  • Stores opened on different dates — S014 mid-year. Snapshot rows before a store existed would poison averages. Decision: the inventory population is carved by day >= opened_date — Priya's knowledge, now a declared predicate.

The design

Two populations (universes):


One thing this Manifold deliberately does not carry: the team's data contract — "you can't return
more than you bought." An early draft declared it here, adjudicated like everything else. It was
retired by a ruling worth stating, because it is the document's admission test: everything a
Manifold's trial proves is a precondition of something it serves. The contract is true of this
warehouse — but nothing served depends on proving it, so it is not meaning; it is attestation, and it
lives upstream. What remains in the Manifold is exactly the set of claims the answers stand on.
UNIVERSE transaction = customer * store * product * day   BASIS events
UNIVERSE inventory   = store * day
    WHERE day >= store.opened                       BASIS spine

(Since 0.12 the manifold declares a third — category_profile, the driver spine behind the category faces; it enters the narrative with the crossing.)

Universe names are singular concepts — a universe names what its population is about, never describes the records or the grid. (Notice the quiet split: the physical table is transactions, plural; the universe is transaction, singular. And the inventory universe isn't named for its store×day grid — the grid is its shape, not its meaning.) The universe definition — products × predicate × basis — is the scope contract: the Manifold binds to this declared population, and stays stable no matter what else the warehouse grows.

events says: a missing combination means nothing happened — an honest zero. spine says: this is a grid that should be complete — a missing row is a gap, and answers over gaps must say so.

The atlas — the grains and the paths between them:

LEVEL customer, store, product, day        (base grains)
LEVEL region, category
LEVEL cal.week, cal.month, cal.quarter, cal.year

HIERARCHY location {
    store -> region
}
HIERARCHY calendar {
    day -> cal.month -> cal.quarter -> cal.year
    day -> cal.week
}
RELATE product <-> category                ("up to 3 categories per product")

One declaration form for all functional paths: a hierarchy is a named family of them — a single hop (location), a chain, or a small branching structure (calendar needs the branch, because weeks don't nest in months). Every declared path is a claim, tested against the actual tables. Lineage names name meaningslocation, calendar — and a bar like BLOCKED { calendar } bars the whole lineage, week branch included.

The measures — named in the team's own vocabulary, each with a one-line description (Dana's folklore rule: if a new hire can't understand the name from its description, the name isn't done). Note what you don't see below: table names, column names. The Manifold spec is purely logical — it's the document everyone reads, so nothing physical crosses into it. How each name attaches to the warehouse lives in a separate document: the physical→logical map.

The Manifold spec (what everyone sees):

MEASURE revenue         ON transaction    -- "sale amount, summed"
MEASURE orders          ON transaction    -- "count of sale lines"
MEASURE buyers          ON transaction    -- "distinct customers who bought"
MEASURE units_sold      ON transaction    -- "units sold"
MEASURE units_returned  ON transaction
    -- "units returned; nulls predate returns tracking and count as no return"

MEASURE stock ON inventory               -- "the stock level snapshot"
    FAMILY {
        sum   BLOCKED { calendar }   -- "stock summed across time doesn't reconcile"
        last  ORDER day              -- "position = the latest snapshot"
    }

DERIVED aov         = revenue / orders
DERIVED return_rate = units_returned / units_sold

    -- the team's data contract, checked against the data, not hoped

The physical→logical map (the data team's document) — and notice its shape: many to one. A warehouse accumulates multiple physical incarnations of each concept — copies, denormalizations, summaries — and the map's job is to resolve each many down to one authoritative binding, with the rejects kept on the map, with reasons. (Dana's second burn was exactly an unresolved many-to-one: two incarnations of revenue, no declared winner.)

logical            binds to                     other incarnations (rejected, why)
─────────────────  ───────────────────────────  ────────────────────────────────────
transaction        transactions
inventory          eom_inventory                 (name is a fossil — grain is daily)
revenue            sum(amount)                   daily_revenue_summary.revenue (stale)
orders             count(*)                      monthly_avg_order_value.n_txns (stale)
buyers             distinct(customer_id)         monthly_unique_visitors (stale)
units_sold         sum(units)
units_returned     sum(units_returned)
stock              level                         monthly_store_inventory (stale rollup)
region             stores.region                 transactions.customer_region (copy);
                                                 customers.region (customer's, not store's)
store.opened       stores.opened_date
units (row attr)   units                         (same spelling — a coincidence the
                                                 map records and the spec never needs)
calendar paths     calendar(day, ...)            location path: stores(store_id, region)

So the solutioning produces two artifacts: the physical→logical map (many to one — the survey of the territory, rejects and all) and the Manifold spec (purely logical — the output everyone reads). And the wall between them is not an accident of tidiness — the Manifold is a blast wall, purposely built. The spec is self-sufficient: every name in it means something declared in it, so a reader — or an agent — who never sees the map loses nothing but plumbing. The map is the only place the two worlds touch. Even when a logical name happens to share its spelling with a physical column (the row attribute units), the spec's units means the declared attribute; the coincidence lives on the map. Two things follow, and both matter later. Dana's "no raw access" constraint costs nothing to enforce — the assistant isn't forbidden from the tables; from where it stands, the tables don't exist. And the warehouse can be re-plumbed — tables renamed, feeds replaced, summaries deleted — without a word of the spec changing: re-bind the map, re-run the trials, done.

Three renames from the physical layer, each with a reason: buyers (not "visitors" — these are customers who bought, and that's what Dana's question 6 says); units_sold (the column is just units; the measure says what it means); and stock for the inventory measure (the source column is called level, which collides with the word for grains — a new hire reading "level by month" learns nothing; "stock by month" is self-explaining).

generated One authored graph, two projections — the logical Manifold spec everyone reads, and the many-to-one physical→logical map with its rejects kept. ✓ the wall holds

The Manifold spec logical — what everyone reads

  • revenue sale amount, summed
  • orders count of sale lines
  • buyers distinct customers who bought
  • units_sold units sold
  • units_returned units returned; nulls predate returns tracking and count as no return
  • priority the category's assignment rank — 1 ranks first; drives the primary face
  • alloc_weight the category's relative allocation weight — normalized per product at the crossing; drives the split face
  • stock the stock level snapshot
  • aov = revenue / orders
  • return_rate = units_returned / units_sold

The physical→logical map many-to-one — the plumbing

logicalbinds torejected (why)
transaction transactions
inventory eom_inventory
eom_inventory name is a fossil — the rows are one snapshot per store per DAY, not month-end
category_profile category_attributes
revenue sum(amount)
daily_revenue_summary.revenue a stale rollup; the base fact is authoritative
orders count(*)
monthly_avg_order_value.n_txns a stale rollup
buyers distinct(customer_id)
monthly_unique_visitors.visitors a stale rollup — and 'visitors' is not the same concept as buyers
units_sold sum(units)
units_returned sum(units_returned)
priority last(priority)
alloc_weight last(alloc_weight)
stock level
monthly_store_inventory.level a stale rollup
store.opened stores.opened_date
region stores.region
transactions.customer_region a denormalized copy carried on the fact
customers.region the customer's home region, not the store's
location paths stores(store_id, region)
calendar paths calendar(day, month); calendar(month, quarter); calendar(quarter, year); calendar(day, week)

What we left out, and why

Scoping is mostly saying no, with reasons a reader can check:

  • The four summary tables — never sources. They're stale, the base tables answer everything, and their only honest purpose — query speed — is the engine's cache's job, not a table's.
  • satisfaction — real data, no question asks for it. Available when one does.
  • Customer segment, customer-region analysis — same: in the warehouse, not in the questions.
  • engagement_scores — covers only ~half the customers; any customer-level answer from it would be quietly wrong for the other half. Until coverage is understood, it's out — and that reason is recorded, not just the decision.
  • support_tickets — another team's domain; Sales Ops didn't ask.
  • Revenue by category — deliberately not offered: the many-to-many makes an honest single-count impossible, and the declared relationship exists so the refusal can say exactly that.

Next chapter: the Manifold goes live — every claim above gets checked against the actual data and comes back with a verdict, and then the questions get asked.


Cascadia Retail — live

Chapter 3 of the case demo: the Manifold goes live. Every claim from chapter 2 gets checked, the questions get asked, and Dana's "what done looks like" gets tested. Everything shown here is real output from the running system — recorded, never hand-edited.


First, the trials

A Manifold isn't accepted on the author's word. Every claim in the chapter-2 spec is checked against the actual warehouse before anything serves:

Claim Verdict
location: store → region is functional (one region per store) corroborated — 24/24 stores, one region each
calendar: day → month → quarter → year chains cleanly corroborated — 731/731 days, every hop
calendar: day → week (the branch that can't chain) corroborated
product ↔ category is many-to-many recorded — a declared relationship, on the record so refusals can name it; its multiplicity isn't yet a tried claim
transaction BASIS events (absence = nothing happened) untestable — the author's call, on the record
inventory BASIS spine (absence = a gap) untestable — same

And one row from the draft of this table is deliberately gone: the opened-date carve (day >= store.opened) is not a claim on trial — it's part of the population's definition. Nothing outside it exists to the Manifold, so there is nothing to test; a definition confines, a claim gets tried. (For the record: the raw feed happens to contain no pre-open snapshots today anyway.)

Two things to notice. The vocabulary is honest about what data can do: checked claims are corroborated — the data supports them today; only mathematical facts earn verified. And the basis claims are untestable — no data can prove what a missing row means; that's a human's call, and the system records it as one instead of pretending otherwise. And the verdicts aren't documentation — they're load-bearing: if the data ever contradicts a claim, that claim stops serving until someone fixes the data or the declaration.

The Explorer

The claims are tried; the Manifold stands. Before using it, look at it: everything declared above is browsable. The Explorer renders the Manifold spec — the logical document, nothing physical — with every declaration wearing its verdict: the universes with their basis and what absence means, the atlas with its two hierarchies, each measure with its laws, each claim with its trial. Every fact carries the triad: defined as (the declaration), tested (the verdict), show me (a query to copy). A new hire's first hour lives here: it's the folklore, out of Priya's head, on a page.

THE UNIVERSE VISUAL · FIGURE 1

Cascadia — three universes

Universe-first, and multi-universe as foundational: each population is a first-class block holding its basis and its metric families. The edges carry the distinction — solid hops are functional (many-to-one) paths; the one dotted edge is the product↔category frontier, crossed only through its three declared faces. Labels are verbatim from describe.

inventoryBASIS spineuntestableabsence is a GAPcarve day >= store.openedbase: day · storeMEASUREstock [sum · last]GRAINSone declared level, two presences — the '=' marks the shared-atlas declarationdayone declared level, two presences — the '=' marks the shared-atlas declarationstorefunctional grains: calendar, location ✓identical to transaction — see the = edgestransactionBASIS eventsuntestableabsence is a lawful ZERObase: customer · day · product · storeMEASURESrevenue [sum]orders [count]buyers [distinct]units_sold [sum]units_returned [sum]GRAINSone declared level, two presences — the '=' marks the shared-atlas declarationdayone declared level, two presences — the '=' marks the shared-atlas declarationstorefunctional grains — ▸ = corroborated M:1 hopstore ▸ regionday ▸ cal.month ▸ cal.quarter ▸ cal.year· cal.weekFRONTIER — the M:N reach (in-box)productcategory appears here through the M:N bridge — an occurrence presence, not a functional grain; servable only through a declared facecategorypresenceproduct_categories(product_id, category_id)revenue reaches every category a product sits in — deliberately multi-counted; totals exceed the grand totaltouchrevenue will be assigned to each product's top-priority category — single-counted; secondary memberships are not represented; totals match the grand totalprimaryrevenue will be split across a product's categories as a weighted sum, using alloc_weight as relative weights — totals reconcile to the grand totalsplitcategory_profileBASIS spineuntestableabsence is a GAPbase: categoryMEASURESpriority [last]alloc_weight [last]categorybaseone declared level, two presences — the '=' marks the shared-atlas declaration=one declared level, two presences — the '=' marks the shared-atlas declaration=one declared level, two presences — the '=' marks the shared-atlas declaration. Occurrences must conform to members — adjudication dated, rides OF-5.=priority drives primaryalloc_weight drives splitsolid → = functional hop, in a universe (M:1)· · · the M:N frontier reach, in-box (faces mandatory)= declared identity across universesbase:the block's base (anchor)
Cascadia's manifold: three universes. Each block shows its base (colored) and the grains its solid, functional edges reach; '=' links shared declarations — store, day, and category — across universes. The dotted product↔category edge inside transaction is the frontier: an M:N reach, not a functional grain, servable only through its three declared faces, whose drivers live in category_profile.

The hover, shown as a static pair (in the shipped visual, hovering a family member paints the stacks by traversability — CSS-only, no JS). Here the first burn, taught: the same stock over the same calendar stack, two members.

stock.sum @ calendarcal.yearcal.quartercal.monthBARRED
barred (✕) — summing a stock across time doesn't reconcile; the B-anchor bars calendar.
stock.last @ calendarcal.yearcal.quartercal.monthTRAVELS
travels (✓) — the period-end position is a clean read; last takes the latest snapshot.

Left out by design (the spec + the map hold the detail): logical attributes, the map's reject rows, operator properties, provenance. The visual holds the shape; a caption links the Manifold spec and the physical→logical map.

Browse the whole Manifold live — every declaration with its law, its verdict, and a query to try — at /explorer.

The questions, asked — what the manifold is FOR

The veteran's way. Priya types tersely, in the query language:

SELECT revenue, orders AT {region, cal.quarter}

serve: thirty-two rows, four regions × eight quarters — two years of history. Every answer arrives as a pair — the numbers, and the basis they stand on (population: transaction; paths: location, calendar — both verified).

The first burn, retried. A new analyst — this is week two for him — asks for "total inventory by month, per store":

SELECT stock.sum AT {store, cal.month}

serve, with a caveat that can't be missed: the per-month figures come back, and with them: stock summed across the blocked calendar lineage — per-bucket totals do not reconcile along this axis; for month-end position, use stock.last. The meaningless exec-deck number now cannot be produced silently. He reads the caveat, switches to stock.last, and gets the position Dana's question 5 actually wanted. The burn didn't need a policy or a training. It needed a declaration.

The ambiguous ask. "What was the average monthly order value for the year?"

SELECT avg(aov) AT {cal.year}

clarify: averaged from which grain? The average of twelve monthly AOVs and the pooled AOV over all transactions are different numbers. The system lists the readings; the asker picks; the number is exact. A vague question came back as a question — Dana's constraint, working.

The question that comes back with its reason attached — and then gets answered. "Revenue by category, please." → clarify: product ↔ category is many-to-many; this aggregate is underdetermined. And the answer now offers a menu of declared, lawful crossings:

category.touch — revenue reaches every category a product sits in — deliberately multi-counted; totals exceed the grand total

Re-ask the honest way — SELECT revenue AT {category.touch} — and it serves, in disclose: twelve category rows, with the arithmetic of the crossing stated on the answer itself: multi-counted by construction across product↔category … totals exceed the grand total (touch total $3,182,555.97 against the single-count grand total of $2,212,391.86 — about 1.44×, which is exactly the bridge's average of 870 memberships over 600 products; the over-count IS the fan-out, and the answer says so), plus the coverage line: 600/600 — every product carrying revenue is categorized; no shortfall; the over-count is the only skew. Nobody gets a silently inflated number; anyone can get the deliberate one, labeled. The someday question arrived years early — and by launch, it had an answer as well as a reason.

And the second burn? Nothing to show — which is the point. The stale summary tables aren't in the answering path at all; there is one revenue, it comes from the authoritative binding on the map, and the two-decks meeting can't happen again.

generated The same asks the packaged demo --play runs, in story order — each a real wire outcome:

clarify input_anchor_ambiguous
SELECT avg(aov) AT {cal.year}

The ambiguous ask

refuse out_of_universe
SELECT stock.last AT {customer}

A true refuse (the wheel needs one)

disclose b_anchor_crossing
SELECT stock.sum AT {store*cal.month}

The first burn, retried

serve
SELECT aov AT {cal.month}

The well-posed sibling

The MCP server

The assistant doesn't get table access — Dana's constraint. It gets an MCP server over the Manifold, which exposes exactly three things:

  • describe — the whole Manifold, machine-readable: every name, description, law, and verdict. The assistant reads this first; it's how it knows what buyers means and that stock.sum is blocked over time. And it is all the assistant can see — the Manifold is a blast wall by construction, so "no raw access" is architecture, not a policy anyone has to enforce.
  • query — FrameQL in, the four-mood answer out: result plus disclosure, always.
  • explain — a dry run: what a query would do — its canonical form, the paths it takes, the verdicts it relies on, the mood it would return — touching no data. The assistant checks before it runs.

And the server carries knowledge — the operating instructions the assistant is given: the grammar; the standing laws (a clarify is the human's question, never the assistant's guess — relay it; use the declared names — an answer that renames things teaches folklore all over again); and the pointer to describe as the source of truth. The veteran's context, packaged and transferable.

The assistant, in conversation — recorded

What follows is the actual recording: human turns scripted, agent turns live against the shipped model, MCP, and prompt — tool calls visible, nothing fabricated. Long result frames are abridged with a marked ellipsis; the complete recordings ship in the repo, every take preserved.

The regional manager (never wrote SQL in her life):

Manager: How did the west do last quarter? Assistant: (couldn't read that as a grounded reply — the agent may only surface figures from a query result. Try rephrasing your question.)

Behind that line, the recording shows the agent consulting describe_manifold and drafting a careful clarifying question — which measure, which quarter (the demo's world is frozen; the assistant has no "today," so "last quarter" has no referent). The grounding guard, strict about what may surface, suppressed the draft. Ugly UX, honest bones: the system would rather say nothing than guess.

Manager: West, Q4 2025. Assistant: Here is the answer: revenue: region="west" cal.quarter="2025Q4" = 31,468.78; region="north" cal.quarter="2025Q2" = 71,424.56; (… 30 more rows — the full region × quarter frame; abridged here, complete in the recording)

The scoped question resolves the honest way this language version allows: the agent anchors AT {region*cal.quarter} and the answer is in the frame — present, grounded, and carrying more rows than the question wanted. A native coordinate filter is the road ahead (it's on the ledger); this is what truthful agent output looks like this side of it.

The new hire, week one:

New hire: How are we doing on returns? Assistant: (consults describe_measure, describe_manifold, checks the plan with explain — would-be: serve — then queries) Here is the answer: return_rate: cal.quarter="2024Q1" = 0.0146 … cal.quarter="2025Q4" = 0.0160; units_returned and units_sold alongside (both years, by quarter; abridged) · disclosure [freshness, immaterial]: served from cache

New hire: By month, this year. Assistant: The engine served the return-rate trend by quarter, with returned and sold units alongside — the values are shown above. One heads-up on the span: this covers the full recorded range (both years, quarter by quarter), not just this year — so you're seeing the whole trend, not a single current figure. Want me to narrow it — by region or store, or a specific quarter — or is this the view you needed?

The span reconciliation is the prompt's LABEL THE SPAN law firing live. Two warts, kept because they happened: the agent fetched the null-convention folklore but served without relaying it this take (the SURFACE THE FOLKLORE law is a bias on a mind, not a guarantee), and it answered "by month" with the quarterly view it already had. The check-before-you-run explain call, the honest cache disclosure, and the offer to narrow are the machinery working exactly as designed.

How these transcripts were made

Four takes, every one preserved in the repo, none hand-edited: take 1 surfaced two agent-prompt gaps (folklore unrelayed; a span mislabeled) → two laws were ratified into the prompt. Take 2 confirmed the first law and surfaced a third gap (the agent's SQL reflex, WHERE region = 'west', isn't this language — a real expressiveness gap, now on the ledger as the leading post-launch language increment) → a third law. Take 3 showed all three laws effective and the remaining limit architectural (the frame-weight above). Take 4 ships — one re-roll after a defective attempt, both preserved; warts documented, not retouched. A law is a bias on a rented mind, not a guarantee — which is precisely why every claim in this system is checked rather than trusted, minds included.

What done looked like — the honest scorecard

Chapter 1 set the bar. Where it stands, in recorded output: the two burns are impossible — the meaningless inventory total cannot pass silently and the stale summaries aren't in the answering path at all. One definition of return rate, declared once, served everywhere. Ambiguity doesn't produce confident wrong numbers — though this take shows the guard suppressing a good clarifying question rather than asking it, which is the right instinct wearing rough edges. The folklore lives in the system and is served on demand — and one live turn shows the assistant forgetting to volunteer it, which is why it lives in the system. A new hire in week one gets the same grounded numbers the veteran gets; the polish of the relay — the single clean row, the never-missed folklore — is the named, ledgered road ahead, not a promise.

The whole case — the warehouse, the map, the spec, and every transcript on this page — ships in the demo. pip install columna, and rerun it yourself.