Orientation
One path to a number, three consumers of it
There is no second way to compute a figure. A dashboard tab, an agent and the chat analyst all reach the same function, so they cannot quote different numbers to the same reader on the same morning.
dispatch.run. The dashed line matters: the Brief re-fetches its own figures rather than scraping what an agent rendered. An agent’s table is a top-N slice of display strings; aggregating it would describe a sample as the brand.Layer 1 · base analytics
Capabilities: the only way to a number
A capability is one registered analysis — a name, a parameter model, a scope list, a function. Adding a domain adds an entry, never a tool.
That constraint sets the shape. Tool definitions load into every model turn, so a tool per domain degrades the model with each addition — and on the MCP side forces every connected user to re-add their connector. Entries are data; the tool surface stays fixed.
- registry.py
- What exists. Parameters are a pydantic model, so the description a model reads and the validation the dispatcher runs come from one class and cannot drift. A duplicate name raises at import.
- dispatch.py
- The single path in: validate, authorise, run, shape. Scope enforcement lives here, not in the route — a second enforcement point is a second place to forget.
- envelope.py
- One response shape, two independent guards.
limitbounds rows; a byte ceiling bounds bytes, because fat rows breach an output cap without breaching a row count.
Truncating is fine. Truncating quietly is not. A cut result that reads as complete is worse than an error — the reader has no way to know.
Several capabilities compute nothing of their own. regional_instock calls two others and judges what they return; the stock distribution matrix is a composition that registers nothing at all. If two surfaces could disagree about a figure, one of them is recomputing.
Layer 2 · agents
An agent is an employee with a job, not a report that runs
It is hired for a beat, told what it is accountable for, and every morning it files the same three things: what is working, what is not, and what to do about it. It exists before its first run and survives every rebuild.
The framing is not decoration. A report is defined by its output, so when it produces nothing you delete it. An employee is defined by its job, so a quiet morning is itself the report — and when it cannot do the work, yesterday’s still stands and somebody is told why.
Agents are named for the job, never the metric: Buying Planner, Transfer Planner, Run Rate Watch. Nobody asks a colleague for the days-of-cover report; they ask who is watching cover.
The anatomy
| It has | Which means |
|---|---|
| a job title | What it is for, in the words a colleague would use. Changing it does not change what it does. |
| a job description | Its purpose in prose, editable without a deploy. The person who knows what cover means for the business is not the person with commit rights, so this is a product surface, not a comment. |
| a beat | The slice of the business it watches every morning, whether or not anything happened in it. |
| judgment it did not invent | A skill doc: what counts as material, whose problem it is, which frame to count. Authored by an operator, argued with in prose. |
| a fixed reporting shape | Three hits, three misses, three next steps. The same shape daily, so a reader learns where to look instead of re-reading. |
| a hard boundary | It computes no numbers of its own. It runs zero SQL and calls capabilities, which is what makes it unable to disagree with the dashboard. |
| an employment record | Every run kept thirty days: what it said, on which figures, and whether it managed to say it at all. |
| a team | Agents on one team fire together on one schedule. Two teams today — supply, and demand signal. |
What it is expected to file
The morning report is a reading of the data, not a summary of it. Hits are what is going right and worth leaving alone — the half most reporting drops, and the half that stops a reader treating every morning as a crisis. Misses are what moved against the business, ranked by what they cost rather than by how bad they look. Next steps are what a person could actually do today.
An agent is also expected to say when it cannot answer. A refusal — no day in the last ten carries enough settled volume to judge a run rate — is a report, and a more useful one than a number computed on a feed that has stopped arriving.
A quiet morning must look different from a broken one. An agent with nothing to say and an agent that failed produce the same silence unless the system is built to tell them apart. So a failure is recorded, the previous reading is left standing, and the date on it stays honest.
Two ids, and confusing them is the defect
The agent id is minted once and never changes — it is the employee. The response id is minted per run and names one morning’s numbers — it is the memo. What a reader pins is the employee; a pin keyed to a memo would dangle every night.
Push, pull, and the gate
Agents push: they arrive on a schedule, unasked. Answering on request goes through the partner analyst instead, which already works with the reader’s standing context in front of it. A second answering path would be a second place for the groundedness rules to live, and a second place for them to drift.
The gate refuses loudly, with the reason in the sentence. An agent that silently answers nothing looks identical to an agent with nothing to say.
The morning pass
A team fires once and works its agents in order, sharing one timestamp, so “did the 06:00 pass happen” is one question. One agent’s failure is not the team’s failure — it is recorded, the pass moves on, and that agent’s previous reading stays up. Yesterday’s cover with an honest date beats an empty page.
The contract that makes agents safe
Code hands over a closed set. The model answers in references.
The most load-bearing rule in the system, applied identically by an agent’s findings pass and the Brief's composition pass.
What that looks like
# code runs the transfer analysis and holds the rows
row 0 Mumbai → PMR 2,408 units 15 SKUs
row 1 PMR → Mumbai 1,534 units 21 SKUs
# the model receives them whole, and replies
lead: row_ref 0, form table,
"Two lanes worth running today"
# code resolves the ref and prints the figures itself
Mumbai → PMR · 2,408 units · 15 SKUs
The model never typed 2,408. If it had, it could as easily have typed 2,480 and nobody would catch it. Pointing at a row means the worst it can do is point at the wrong row — a judgment you can see and argue with, not a fabricated number.
Why a model writes the reading at all
An earlier version produced these readings from templates. It was correct every run and worth nothing by the third — an executive reading the same three sentence-shapes on Wednesday that they read on Tuesday stops reading. What must change morning to morning is not the figures in the sentence but which finding was worth a sentence, and that is a judgment.
Code computes every number and hands over a closed set. Prose decides which of them a reader is owed, and in what words. Code then checks the shape and refuses anything malformed.
How judgment is stored
Skills: a spine, and blocks pulled on condition
A skill is authored judgment in markdown — how to read an answer, what is material, who owns the response. Prose is the only form a non-engineer can argue with.
Skills are not loaded whole. The spine is short and always loaded. Each block is its own file with a when: line naming the condition to pull it, and a manifest that parses when: lines only — never bodies. Instructions the model does not need now are not free; they compete for attention with the ones it does.
# knowledge/today_composition/blocks/
03-which-rung-to-show.md
when: an answer carries more than one rung —
channel, category, family
cost: 600
06-when-the-answer-is-missing.md
when: an entry is unanswered or stale, or a rung
reports it could not be judged
cost: 520
A missing skill is never an error. A consumer treats a miss as “no extra guidance” and falls back to its own prompt. A skill not yet written must never take a surface down.
Nothing is cached. Files are hand-edited as they harden; an edit must land on the next question without a restart.
Two stores. Domain skills ride into a capability's payload, so the doc is reachable before you run an analysis and after you hold its result. Agent skills sit beside the code that reads them — a skill two directories from its only consumer goes stale when that consumer is rewritten.
Layer 3 · the assembler
The Brief: composed once, served all day
The findings pass reads one agent. The Composer is the same seam one level up: it reads every watched risk and every agent at once, and decides what deserves a reader's first ten minutes.
It used to compose on every request — twenty readers got twenty different pages of one morning, behind a proxy ceiling of 120 seconds. Now the route is a read.
Four files, four jobs
- the register
- What is watched: each risk with a question, a capability, a freshness bound and a
must_printflag. - pack build
- Runs them. No model is involved and none ever will be — separating the choosing from the fetching is what stops a model deciding what the page looks at.
- the composition
- Picks emphasis, form and the lead, by reference.
- the strip
- Six figures code owns outright. No model sees them, orders them, or writes a word of them.
What an entry can come back as
| Outcome | Why it is not merged with its neighbour |
|---|---|
| answered | A figure, fresh enough to use. |
| stale | Older than its max_age. Separate, because a frozen feed still returns a number and the number still looks fine. |
| unanswered | The capability failed or refused. A fault — page someone. |
| not watched | An IOU. Nothing was built to answer it — a decision nobody made, not a fault. Opposite response. |
Every strip tile names its frame (“across rows that fell short”), its denominator (“147 of 218 products costed”), its scope, and its freshness. A missing figure renders as unread, never as zero.
One place code overrules the model
Sections are a grouping over the register, not a second register. Their order is the model's ranking, with one exception: actions always lead. “Move 2,408 units from Mumbai to PMR” asks the reader to do something today; “cover is thin on four families” asks them to know something. A reader who only reads the top should still reach what they can act on.
row_ref — prose saying “eleven lakh behind” would sit above a tile reading thirty, and a reader cannot tell which half to believe.The contract
What each layer may not decide
Read down the right-hand column. Every prohibition is one an earlier version violated, on a live surface, in front of readers.
| Layer | Owns | May not decide |
|---|---|---|
| Capabilities | Every figure, its as_of, its caveats, and who may run it. |
What a figure means or whether it is urgent. It publishes unmeasurable rather than substituting a zero. |
| Content builder | Which rows an agent covers, in what order, under what ranking rule. | Any number of its own. It runs no SQL. An agent that recomputes is an agent that can disagree with the dashboard. |
| Skill doc | How to read an answer, what is material, which frame is counted. | Anything deterministic. A filter that is arithmetic stays in Python — moving it into prose makes a fixed rule probabilistic. |
| Findings pass | Which measured finding is worth a sentence, and the words for it. | Any figure not already in the pack. No tools, no database, no second query. |
| The register | What is watched, how fresh it must be, what must appear regardless. | How the morning is laid out. Sections group it; they do not replace it. |
| The Composer | Emphasis, form, and the lead — by reference. | Which risks are watched, what the numbers are, which must appear, how many fit, or what any of it means. |
| The strip | Six figures, their frame, denominator, scope and freshness. | Nothing — and no model touches it. That is the determinism the page was missing. |
Operating reality
When it breaks, and how you find out
The recurring defect is never a crash. It is a surface that keeps rendering perfectly while quietly answering a different question.
- A step that completes without doing its job. A composition can fail and still write a page that is correct but carries no bullets — and the build records
ok, because the step did complete. A separate check watches for a stored page whose source beginsfallback:. - A guard that reads as coverage while providing none. A test that cannot fail; a completeness check blind to two of six writers. The rule: break the guard, watch it go red, revert. A guard never seen red is not a guard.
- An absent row. A sale whose SKU resolved to nothing used to be dropped — not flagged, just absent, which is invisible. Such rows now land on a sentinel listing, get counted, and are re-pointed onto the real product in place when the mapping arrives.
What varies morning to morning is emphasis and wording; the figures are fixed and diffable. A mid-day agent re-run once silently rewrote what a whole readership had already seen, because the findings pass regenerates prose on every run. Hence: composed once, stored, served all day — and a rebuild is an operator at a command line, never a button.
A reader must never have to guess what a figure counted. One engine guarantees the rows agree; it never guarantees two callers summarised them the same way. So every surface names its frame — which rows, against what denominator, as of when.
This page, too
Etalon is the product name; the code still says creya throughout — schema, modules, hostname. Grep for that, not for Etalon.
Every count and list in the sidebars is generated from the code, not typed. A test in the repo rebuilds them and fails if this page disagrees with what is registered, so adding a capability or an agent turns the suite red until the page is rebuilt. The prose is hand-written and carries no such guarantee.