Etalon · application layer · Terminal6

Capabilities, agents, and the Brief

Three layers, each defined by what it is not allowed to decide. Capabilities own every number. Agents own judgment. The Brief owns emphasis — nothing else.

20capabilities
4domains
2teams
7agents
7watched risks
1path to a number

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.

Layers A–D — Postgres (RDS) and BigQuery raw ingestion · canonical tables · computed views · decision records SQL, in one place per analysis LAYER 1 — CAPABILITY REGISTRY registry.py what exists dispatch.run() validate · scope · run envelope.py shape · two guards Operator dashboard tabs, matrix, xlsx export Layer 2 — agents zero SQL of their own Partner analyst chat, over MCP tools published readings figures, fetched again Layer 3 — the Brief composed once at 06:00 · stored · served all day every reader, one identical page
Every figure on every surface comes back from 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. limit bounds 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

What every agent is given
It hasWhich means
a job titleWhat it is for, in the words a colleague would use. Changing it does not change what it does.
a job descriptionIts 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 beatThe slice of the business it watches every morning, whether or not anything happened in it.
judgment it did not inventA 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 shapeThree hits, three misses, three next steps. The same shape daily, so a reader learns where to look instead of re-reading.
a hard boundaryIt 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 recordEvery run kept thirty days: what it said, on which figures, and whether it managed to say it at all.
a teamAgents 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.

CODE OWNS THIS MODEL OWNS THIS no tools · no database · no second query dispatch.run() every figure, measured the closed pack rows, totals, caveats, skill sent whole emphasis, form and words which finding is worth a sentence row_refs + headline + prose never a figure it typed itself back across validate · enforce · resolve shape, must_print, refs → rows valid malformed fallback plain, correct published reading / stored page every figure traceable to a row
A figure the model typed cannot reach a reader, because the model never emits one. Both failure paths — a call that errors and a call that returns the wrong shape — land on the deterministic fallback. The reader sees a duller page, never an empty one.

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_print flag.
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

OutcomeWhy it is not merged with its neighbour
answeredA figure, fresh enough to use.
staleOlder than its max_age. Separate, because a frozen feed still returns a number and the number still looks fine.
unansweredThe capability failed or refused. A fault — page someone.
not watchedAn 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.

06:00 IST 06:00 → MIDNIGHT the agent runner every agent, sequential, one live each the Composer register → pack → compose → strip → store the Brief a read, every time agents first, never after Build the page before the agents and every drill-down link silently degrades to a raw population — on the mornings the agents rebuilt perfectly well. One artifact, one morning, every reader identical.
The order is load-bearing, and the composition and its pack must be snapshotted together. A stored composition over live figures strands every 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 responsibilities
LayerOwnsMay 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 begins fallback:.
  • 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.