Temper

// architecture

Temper

A reinforcement-learning execution agent graded against its own analytic oracle — and, underneath it, a narrower problem than the one an ML project usually has: what it takes to grade a learner against a closed form without fooling yourself. Temper is the only project on this site where the right answer is already known before training starts, and that turns out to change everything about how a result has to be defended.

A narrative rendering of Temper's golden-source documents — ARCHITECTURE.md (the constitution: the environment contract, the frozen invariants, the decisions, and §9's amendment log), README.md (the honesty ladder — what each phase establishes and, rung by rung, what it does not) and ROADMAP.md (the milestones, whose status column is where every measured claim carries its denominator) and docs/house-notes.md (the practices that outgrew a single milestone, one of which the last-but-one section quotes in full rather than paraphrasing). Those repo documents are authoritative for what is true; this page is an argument built out of them, and is authoritative only for how it is told here. The project page at /projects/temper is the shorter read.
This page reflects the repo as of M5, which is the last milestone. That sentence is load-bearing rather than boilerplate, and it has just changed what it says. Temper's constitution is not a settled document and was never meant to be — §9 is an amendment log that every milestone adds to, five entries on a single day during M4b and six more for M5 — so the sentence existed to let a reader tell lag from neglect while the document was still moving. Every roadmap row is now ticked, so nothing scheduled will move it again; the manifest and the drift check stay, because the point of measuring lag rather than remembering it does not depend on expecting any.

Beating the closed form is a red flag, not a result

Temper trains a reinforcement-learning agent to work a parent order — to liquidate a position over a trading day, on the fixed grid the vendored goldens arrive on: 6.5 hours in thirteen half-hour bins, costs in basis points of notional — and grades it against the Almgren–Chriss closed forms that FrontierView, another project in this portfolio, already implements. So far that is an ordinary reinforcement-learning-for-execution project. What is not ordinary is the direction the claim runs.

Under the exact assumptions Almgren–Chriss makes, the closed form is optimal. So inside that world the sentence "the agent beats Almgren–Chriss" is not a result to be celebrated; it is a symptom. The constitution says so in its first section, before anything else: claiming to beat AC inside AC's own assumptions is a red flag, not a result. Phase 1's claim is therefore the weaker sentence and the harder one — that the agent independently converges to the closed form within a tolerance written down before training started.

Everything else on this page follows from that inversion. If the only two outcomes you are willing to accept are "it matched" and "something in the harness is wrong", then you need three things you would not otherwise have bothered with: a reference derived somewhere the agent cannot reach, a threshold you are not permitted to move afterwards, and a way of telling those two outcomes apart that is not your own judgement of whether a training curve looks healthy.

The project is staged so that the licence to claim an advantage is earned rather than assumed:

PhaseThe claimStatus
1 · RediscoveryThe agent independently converges to the analytic schedule within a pre-stated tolerance. The closed form plays the role Anvil's reference engine plays: a second, independent answer the implementation must match.Done — M0 to M3
2 · Earned advantageBreak the assumptions the closed form needs, and show where learning genuinely wins — out-of-sample, ≥5 seeds, dispersion reported, with the now-mis-specified AC schedule and TWAP still on every chart.Done — M4a, M4b and M5
3 · The wire (stretch)The trained policy works a parent order on a live Anvil book as that protocol's third independent client. A demonstration that the policy speaks a real venue wire — explicitly not an evaluation venue.Done — M6

Every row of that table is now ticked, and the last one to close was M5 — alpha-aware execution: a weak short-horizon signal in the observation, an agent that learns to tilt the schedule, and a signal-shuffled control to catch it overfitting. Its definition of done was written before any of it ran, which is the point of the next section.

The nine rules the argument rests on

§6 of the constitution freezes nine invariants, with the instruction not to refactor through them. They are not style preferences — each one closes a specific way an RL project misleads its own author, and the why column is the repo's own.

InvariantWhy it exists
1 · Every reported number regenerates from a committed config and seedMulti-session agentic work converges only when red and green are objective, and an evidence-based portfolio claim has to be reproducible on demand. Entries in results/ carry the config hash and the git revision that produced them.
2 · The oracle is normativeThe differential-oracle pattern. The oracle must match the vendored goldens within a stated tolerance and the agent is graded against the oracle — so the success criterion can never migrate toward whatever the agent happens to do.
3 · Success thresholds are pre-statedPost-hoc thresholds are how RL projects lie to their authors. ε, seed counts and CI levels are fixed in the milestone brief before training runs, and changed only by amending the brief before work starts.
4 · No metric without baselines and dispersionSingle-run RL numbers are noise. TWAP and AC at matched λ appear on every chart and table, at five seeds or more, reported distributionally — the same ethos as Anvil's latency distributions, where the tail is the story.
5 · Train and eval seed pools are disjointAgents overfit simulators, and the honesty of the object depends on them not being able to. Phase-2 claims are made only on held-out seeds and configs.
6 · No environment feature without an independent expectation testEnvironment bugs masquerade as agent skill — the classic RL failure mode, and the one this project is structured to catch. Any market-model change ships with a check that a fixed policy's simulated moments match an analytic or independently computed value.
7 · One objective, encoded onceSilent objective drift voids every cross-experiment comparison and the rediscovery claim itself. The training reward, the evaluation metric and the oracle optimise the same functional; any change is an amendment.
8 · The package performs no network I/OThe engine-seam pattern inherited from Anvil and DepthCharge. The Anvil participant lives in client/, consuming the package, so the core stays replayable and testable on any host.
9 · CPU is sufficientKeeps sessions unblocked, milestones honestly sized and the project portable. Milestone configs must train on the reference box in an evening; a GPU may only ever accelerate, never unblock.

Invariant 3 is the one that does the most work and costs the least. ε, the seed count and the confidence level are fixed in the milestone's brief under docs/briefs/ before any training code runs, and loosening one means amending the brief before the work, in the commit history. It has bitten usefully at least once, and the bite is the subject of a section below: the tolerance inherited from three earlier milestones turned out to be roughly twice the size of the effect the next milestone was measuring, and that was visible only because the bar had been written down where it could be compared against the oracle in advance.

An oracle whose job is to say the agent is wrong

The reference is the differential-oracle pattern from Anvil, applied to reinforcement learning. An independently derived engine computes what optimal is, so the agent's score is never a matter of opinion — and invariant 2 fixes the direction of authority: the oracle is normative, and the success criterion never migrates toward whatever the agent happens to do.

The tempting shortcut is to import FrontierView's model and grade against that. Temper does not, and the boundary it uses instead is versioned artefacts rather than shared code. FrontierView's closed forms are exported by a script that is read-only with respect to that repo, vendored under tests/golden/vendor/ with provenance — source commit, generation date, parameter set — and pinned at f87795f6. Nothing upstream changed for Temper's benefit, in FrontierView or in Anvil. The fixture is never hand-edited and never synthesised from Temper's own closed forms, because that would collapse the differential into a tautology; re-vendoring is a make target run against a FrontierView checkout, and the provenance and upstream quirks the export surfaced are recorded in docs/vendor/frontierview-goldens.md.

M0's acceptance was the oracle agreeing with an implementation this repo did not write: sixteen vendored cases plus a seventeen-point frontier, matched to float round-off — about 1e-15, ten orders of magnitude inside the 1e-6 tolerance that had been stated before the code existed.

// the differential loop
// the reference — an independent second derivationFrontierViewseparate repo · unchangedtests/golden/vendor/read-only export · pinned f87795f6temper/oracle/re-derived · matched to ~1e-15what optimal istemper/eval/grading.pythe world is checked before a number is computeda metric may not grade a world it does not chargecapture fraction c · absolute excess in bps beside itresults/config hash + git rev// the learner — and the world that charges itconfigs/one per experimentExecutionEnvone step loop · three seamsPPOsingle filepolicydeterministicwhat it didtemper/oracle never imports temper/env — and a golden is never synthesised fromTemper’s own closed forms, which would collapse the differential into a tautology
The reference and the world are two derivations that never meet. temper/oracle may not import temper/env, and its authority comes from agreeing with fixtures exported out of a repository that changed nothing for Temper’s benefit — sixteen vendored cases and a seventeen-point frontier, matched to float round-off, ten orders of magnitude inside the tolerance stated before the code was written. The grader sits between the two and refuses to score a policy against a world that is not the one charging it. Nothing the agent trains against also decides whether it won.

The independence was re-audited, and the number that justified it had moved

An early audit sized the parallel formula code at about 55 executable lines, when the oracle was essentially a re-derivation of FrontierView, and filed "revisit the code-sharing decision" as a standing item. M3 reopened it as promised. The §9 entry The oracle stays independent, and the question is closed rather than carried closes it, and what closed it is that the number no longer describes the repo: temper/oracle/ is now around 1,080 non-blank lines across seven modules, of which thirteen functions reproduce vendored formulas and twenty-four have no upstream counterpart at all. temper/oracle/powerlaw.py alone — 564 lines of certified power-law optimum, two independent solvers, KKT and curvature machinery — has no FrontierView analogue whatsoever. The shared surface is about 5 % of the module, so the trade on offer is a dependency that saves one line in twenty while destroying the property the other nineteen exist to provide.

What the independence bought is not hypothetical, and it is the subject of the next section. The entry also records what would reopen the question — if FrontierView adopts the discrete convention, the two implementations converge and the parallel surface stops being a second derivation. That is the shape of a decision worth keeping: it names the evidence that would overturn it.

A reference is only half of it — the other half is getting a number at all

The oracle says what optimal is. The grader has to say what the agent did, and doing that badly is its own way of being wrong. Invariant 6 forbids any environment feature without an independent expectation test, on the grounds that environment bugs masquerade as agent skill — the classic failure mode, and the one the project is structured to catch. M1 discharged it by running TWAP and both AC schedules as policies through the real ExecutionEnv step loop and requiring their simulated cost moments to match the closed forms across the full 3 × 3 golden grid at 200,000 episodes a cell. The deep tier is 27 cells at that size, green in 5 minutes 17 seconds with no cell using more than 55 % of its band, and 70,200,000 calls into the real step loop asserted rather than assumed.

The interesting part is how little of that is statistical. An exact per-episode identity pins the realised noise to the specific draws the environment made, so the cost assembly holds by construction and the Monte-Carlo tiers certify only that the shocks are independent and normal. Alongside it: six exact per-episode identities, an exact step count, a variational certificate that the schedule M2 grades against really is the minimiser, and an observation-minimality guard that closed M2's leak before M2 existed.

Grading the agent itself takes a different route again, and the §9 entry A deterministic policy on a price-free observation is graded analytically, not by Monte Carlo separates the two. temper/eval/rollout.py estimates by sampling and is right for M1, whose subject is the simulator. temper/eval/grading.py computes exactly — one deterministic rollout, then the closed form on the schedule it realised — and from M2 onward is the only route by which an agent may be graded. That is licensed by the Phase-1 observation carrying no price: inventory evolves purely from actions, so a deterministic policy induces an open-loop schedule whose moments are a closed form.

It is not merely faster. At M2's case the objective is around 2.4 bps while the per-episode cost standard deviation is around 95, so resolving that milestone's ε of 0.066 bps by sampling would need on the order of 107 episodes per seed — and would still report an interval where the analytic route reports a number. What makes the shortcut sound rather than merely convenient is an assertion, not an argument: the policy is rolled out on two unrelated shock streams and the trajectories are required to be bitwise equal. It fails loudly the moment price reaches the observation, which is also the moment analytic grading would silently start lying.

M4b is the first milestone that cannot obey that rule as written, and the successor entry A liquidity-observing policy is graded by conditional expectation, not by sampling realised cost is careful about which half retires. What retires is the open-loop shortcut. What does not retire is the assertion that licensed it: the price still never enters the decision, so conditioning on the liquidity path removes all of the price randomness analytically and E[cost | L] is still a closed form. The bitwise check grows one axis — pin the liquidity stream, vary the price stream, require the trajectory bitwise equal — so a policy that peeks at price still fails loudly, and is not scored at all rather than scored badly. temper/eval/conditional.py averages that conditional expectation over 20,000 held-out liquidity paths. There is no price sampling anywhere, and the only interval that arrives is over liquidity.

M5 is the milestone that sentence about price reaching the observation was waiting for, because M5 puts it there deliberately: the agent is handed a one-step-ahead signal and asked to tilt the schedule for it. So the guard that has refused anything moving with the price draw since M1a has to move its line for the first time, and the §9 entry A price-bearing observation is permitted when the shock it predicts is not yet committed — and “committed” is a bin earlier than “landed” moves it in two clauses rather than one. Pin the signal stream, vary the price stream, require the observations bitwise equal — M1a's comparison with one axis pinned. And over many episodes, every observation coordinate past time-left and inventory-left must be uncorrelated with every shock at or before the decision point it was shown at. What is permitted is a coordinate correlated only with shocks the current decision can still change the cost of; what is not is the realised price in any form, any realised cost including the running shortfall, and a signal about a shock that is already committed however far ahead it lands.

The entry's sharpest paragraph is about its own brief. The brief asked for a signal about a shock that has not yet landed, and that phrasing is wrong by exactly one bin. step(k) lands the shock and then trades, so at the decision point for bin k that shock has indeed not landed — and a signal about it is worth nothing, because it is charged on inventory already held while the decision being taken now sets the next bin's. The permitted set is one bin further out than the brief said, and the brief's wording admits the single case in between. That hole would have been worth one bin per decision and would have been invisible in every number the milestone reports: a seam pointed one bin short returns a smaller advantage with every gate still green. The amended guard is pointed at five cases, of which three must fail, and it returns a verdict rather than raising so that it can be — M5's own world passes at a worst committed correlation of 0.032 against a strongest actionable one of 0.42, and the already-committed case is refused at 0.416 with its actionable correlation down at 0.041, the pattern exactly reversed.

Grading has to follow the observation, and M5 is where that stops being a sentence in prose. Every conditional grade since M4b was legitimate for a reason argued in a paragraph — M4b conditioned on the liquidity path because that is what the observation carried, and M5 conditions on the signal path for the same reason. The §9 entry A conditional grade's conditioning set must equal the policy's observation set, and conditioning changes the variance the differential standardises against turns it into a check that runs before a number exists, reading the seams the environment actually exposes rather than the one a config calls the signal. Both failure directions are silent and they are opposite. Condition on less than the policy saw and the grade is a conditional expectation with respect to the wrong σ-algebra, biased in the direction of the policy's own cleverness. Condition on more and the grade removes noise the policy really faced, so its interval collapses and it is scored as more deterministic than it is — the extreme being conditioning on the realised price, where expected cost becomes realised cost and every agent looks perfect. Both are exercised as refusals rather than described.

One more guard sits between the two. The §9 entry A metric grades the world that charges it keys temper/eval/metrics.py's registries by cost encoding: the environment publishes the functional it charges, read off the injected impact model, a reference row carries the world its optimum was solved in, and the grader checks the two agree before it computes anything. The rule it replaced was a flat ban that could never have caught a linear metric grading a power-law environment, because linear was permitted to everything equally — and that is now the live failure mode. As the entry puts it, the check that was wide enough to be free was also wide enough to be blind.

Two decay rates, 2.2× apart, both sinh

This is what the second derivation bought, and it is the single best argument in the repo for building one. FrontierView's Almgren–Chriss decay rate κ is a continuum-limit expression; the discrete problem's own stationarity condition gives a different one. At realistic order sizes the two differ by about 2.2×. Both produce sinh-shaped schedules. The difference is invisible by eye on a chart.

Grading rediscovery against the vendored rate would have made a correctly trained agent score up to 18 % better than the "optimum" on the frozen objective, at 100,000 shares of AAPL with λ = 1e-4. That is not a subtle failure — it reads exactly as "the agent beats Almgren–Chriss inside AC's own assumptions", which the constitution's first section names a red flag. A project without an independent second derivation ships that result and calls it a finding.

The oracle now carries both. ac_kappa reproduces FrontierView's expression and is what the vendored goldens pin, so invariant 2 still holds exactly. optimal_kappa solves the discrete stationarity condition for the frozen objective, and is what M2 onward grades the agent against. Keeping both is what makes the rediscovery claim falsifiable without breaking the golden match.

The honest reading of the finding is much narrower than "Almgren–Chriss is wrong", and M3's nine-λ sweep is what narrowed it. The difference is a displacement along the frontier, not a worse schedule. Both trajectories are the same sinh differing only in κ, so there is a λ′ solving optimal_kappa(λ′) = ac_kappa(λ) — and at every grid point the two then agree to 7.3e-11 shares in 100,000, which is float round-off. The vendored schedule is therefore the exactly optimal schedule for a different risk aversion, and its cost-variance point lies on the optimal frontier, never inside it. Describing it as dominated would be wrong.

The displacement factor is not a constant, which is why the finding needs a sweep rather than a point. It runs 5.01 at λ = 10−5, 5.34 at 10−3.5, 29.3 at 10−2 and 147,000 at 10−1, because the discrete rate grows like log μ once μ is large while the continuum one keeps growing as √λ. So the economic statement the repo will make is bounded: the risk-aversion dial is mislabelled by about 5× at usable λ — a narrow and defensible claim about a vendored library, and not a claim that the model is suboptimal. For the same reason the single number M2 reported for the vendored schedule, 0.3435 of the TWAP gap at 10−3.5, is a point on a curve spanning 8.2942 to 0.0001 across the M3 grid, and is never to be quoted as the cost of the convention.

The same care, applied to η

A second amendment from the same session fixes what Phase 1's "linear temporary impact" actually means. The §9 entry Phase-1 temporary impact is linear at the tangent η̃, not at η records it: FrontierView's temporary impact is a 0.6-power law, which admits no sinh closed form, so the library linearises at the tangent to it — at the order's own TWAP participation rate — purely in order to derive a schedule. The constitution had assumed a natively linear model. Naming the tangent explicitly is what keeps invariant 7's "one objective, encoded once" true, so that the M1 environment, the M2 reward and the oracle are all optimising the same functional. cost_moments charges the power law and is what the goldens pin; linear_cost_moments charges the tangent and is the frozen objective.

Two paths that differ by 12–54 % of expected cost on the Phase-1 golden parameter sets — a power law agrees with its own tangent only where they touch — so splitting the reward from the metric across that gap would have trained the agent on one functional and graded it on another, and every rediscovery and frontier claim would have been comparing two different objectives. The power-law charge is quarantined instead of deleted: it may be reported beside a Phase-1 result and may never be one, and the registry refuses to register it as gradable. Which is exactly the seam M4a later opens, by making the power law the world.

A third amendment from that session decides something that is invisible in code and changes 20.6 % of the answer. The entry The shock lands before the bin executes, so the shortfall variance sums inventory before each bin pins the index convention: the shortfall variance sums inventory before each bin, with the first term the whole order, which is not the textbook form over post-bin holdings. Both are one-line sums over a trajectory, and the two conventions differ by exactly one for every schedule — 20.6 % of the variance for TWAP at thirteen bins. The goldens settle it, and the consequence is worth carrying: the variance has a hard floor, because the shock lands before the first trade can be placed and one bin of volatility on the full position cannot be scheduled away. The frontier therefore approaches that floor as λ grows, rather than the origin — the textbook picture, in which risk vanishes at instantaneous execution, is qualitatively wrong under this convention. What the entry checks, and what makes it a finding rather than an assertion, is that the extra term is constant in the decision variables, so the optimum does not move at all: the milestone's certificate stayed green through a 20.6 % change in the variance, and that is the evidence rather than a coincidence.

What a tolerance is a fraction of

A pre-stated threshold only helps if it can distinguish the outcome you care about from its opposite, and M4a's methodological finding is that the project's own inherited threshold could not. It is the entry titled The tolerance's denominator is the available advantage, not the TWAP gap, wherever the closed form is the thing being beaten, and it is worth more than the milestone's headline result.

Through M2 and M3, ε was 5 % of that λ's TWAP gap — the distance between TWAP's cost and the optimum. That is a good unit for rediscovery, because it is portable across λ by construction. In the power-law world the question changes: the closed form is now the thing being beaten, and the quantity being measured is not the distance to TWAP but the distance between the tangent-derived schedule and the world's own optimum. At M4a's λ that available advantage is 0.03674 bps, while 5 % of the same λ's TWAP gap is 0.06628 bps as M3 actually computed it, and 0.07433 bps re-derived in the power-law encoding — 1.8× to 2.0× the entire effect.

An agent held to the inherited bar in the power-law world would therefore have passed while capturing none of the mis-specification. The tolerance could not have told the milestone's success from its complete failure. Two things generalise out of that. A portable denominator is only portable within the question it was written for — the two units differ by 36× here. And the direction of the reporting trap flips with the denominator: the predecessor entry exists because a gap fraction alone made a healthy agent look as though it were degrading at low λ, whereas here the risk runs the other way, since a capture fraction near 1 on an advantage of 0.037 bps is a small absolute claim that reporting the fraction alone would make sound large. Hence both numbers, always, in that order — Tolerances now carries the denominator it is stated in, and the verdict is read on the matching field, so a config has to say which question it is answering.

The predecessor is worth reading beside it, because it is the same lesson pointing the other way. The per-λ tolerance is meaningful only where the testbed is discriminative, and the frontier measures where that stops being true came out of M3's nine-λ sweep: the agent's median excess over the certified optimum stays between +0.004 % and +0.33 % at every λ across four decades, while the gap fraction it is normalised by moves three and a half orders of magnitude — from 0.070 to 0.00002 — because the TWAP gap itself moves from 0.44 % to 374 %. The sweep's one ε miss, at λ = 10−5, is a statement about the denominator rather than the agent: the whole TWAP gap there is 0.0041 bps, so ε is two micro-bps and the agent is 2.9 micro-bps from the optimum. The frontier figure's lower panel draws the gap fraction against λ with ε across it, so a reader can see which points the tolerance is entitled to speak about at all.

Certified, bracketed, floored

Phase 2 breaks the assumptions, which means the closed form stops being the reference and something has to take its place. M4a makes FrontierView's calibrated 0.6-power law the world — injected as a model into the one ExecutionEnv step loop rather than subclassed, so there is still exactly one loop and no config can inherit a Phase-2 world by omission; it has to name one, and a repo-invariant test checks that it did. That world's own optimum has no formula, so it is solved.

And then it is checked five ways, because a reference nobody checked is not a reference:

  • A positive-definite Hessian. Cholesky factorisation succeeds, so the stationary point is a minimum rather than merely a stationary point.
  • A relative KKT residual of 1.2e-15, against a bar of 1e-12 stated in advance.
  • 3,600 perturbations, all uphill. A numerical variational check that no nearby feasible schedule is cheaper.
  • A completely independent bisection solver agreeing to 3.1e-15 of the parent order — a second implementation, not a second run.
  • The same machinery at exponent 1 returning the analytic sinh trajectory to 3.5e-16, which ties the new solver back to the thing that was already pinned. No scipy anywhere: the certificate is the repo's own.

That is what earns the word certified, and M4b is where the word has to be given up. Stochastic liquidity makes the optimum an optimum over adapted policies — a dynamic program — and a stochastic dynamic program has no Cholesky factorisation and no KKT residual. The §9 entry A numerical reference is bracketed, not certified, and the red-flag test moves to the bound that is rigorous says plainly why reusing the word would have been the first dishonest number in the repo, and the discipline is mechanical rather than verbal: the word is absent everywhere the number is reported, the object writes "certified": false in its dictionary, and the results file carries reference_kind: converged and bracketed, not certified — in the field a reader would go looking for it in.

What the dynamic program has instead is two-sided. Its own greedy policy is a real policy, so its mean conditional cost is attainable and therefore a feasible upper bound. M4a's Newton system, generalised to per-bin coefficients and batched over paths, is a perfect-information relaxation and a rigorous lower one. Measured, the bracket is 9.7 % of the adaptive advantage, with the optimum inside it and grid convergence second-order at a Richardson residual of 2.0e-06 bps.

That bracket is too loose to grade against, and exactly tight enough for the thing that matters: it makes the red-flag test rigorous where M4a's rested on an algebraic certificate. It also came out sharper than predicted — perfect information beats any policy on every path, not merely on average, because the clairvoyant solve is the per-path minimum over all schedules and the agent's realised schedule is one of them. So the hard failure condition is a count of paths with no confidence interval in it at all: zero paths below the relaxation, across all ten seeds. And the single most valuable check in the milestone is the one that ties the new machinery to old ground — at zero liquidity volatility the dynamic program must return M4a's certified value, and does, to 1.8e-06 bps.

M5's reference is a dynamic program too — over inventory and signal this time, Richardson-extrapolated — so it takes the same weaker word, and it returns M4a's certified value at zero signal strength the same way M4b's does at zero liquidity volatility. What is different earns a third rung rather than a footnote, and the §9 entry The red-flag test moves to the half of the objective the signal cannot touch, and stops being a threshold is explicit that it is the opposite move to M4b's. M4b made the red flag rigorous by finding a bound below a numerical reference; M5 makes it rigorous by finding a subspace the reference is exact on. The conditional grade splits into impact plus risk plus an alpha term, and impact and risk are convex in the schedule and contain no signal at all — so their expectation is at least M4a's Cholesky-certified optimum in the same world, for any policy whatever. An agent below that floor is a defect with a proof behind it rather than a seed that got unlucky, and nothing about the signal or about the dynamic program's convergence enters the argument.

The alternative was tried on paper first and rejected there, which is the reason it is worth reporting. M4b's construction transplanted — a perfect-information relaxation on the price path — is 1,075× too loose to ever fire at a signal this weak, so that test would have been green by construction and would have certified nothing. The convexity floor is tight instead: across ten seeds and ten shuffled controls the closest approach was 1.876355 bps against a floor of 1.819586, a margin of 0.0568 bps and 84.3 % of the premium the converged optimum itself pays. A test whose margin is a fraction of the quantity being measured is a test that could have fired, so zero red flags across twenty graded policies is a measurement rather than a tautology. The portable form is one sentence: when a reference is numerical, look for the part of the objective that is convex and independent of the thing being learned, and put the certified bound there.

// the four references
the linear worldPhase 1 · M0–M3THE ENV CHARGEStemporary impact linear atthe 0.6-power law's tangentTHE REFERENCE ISthe Almgren–Chriss sinhtrajectory — a closed formCHECKED BY16 vendored cases and a17-point frontier, matchedto ~1e-15 — ten ordersinside the pre-stated 1e-6AND THE WORD IT EARNSEXACTthe power-law worldPhase 2 · M4aTHE ENV CHARGESFrontierView's calibrated0.6-power law — no formulaTHE REFERENCE ISNewton on the KKT system,solved rather than trustedCHECKED BYCholesky PD · no scipyrelative KKT residual1.2e-15 vs a 1e-12 bar3,600 perturbations, uphillan independent bisectionsolver, to 3.1e-15 of Xat exponent 1 it returnsthe sinh, to 3.5e-16AND THE WORD IT EARNSCERTIFIEDthe liquidity worldPhase 2 · M4bTHE ENV CHARGESthe same power law, plus aninvented liquidity processTHE REFERENCE ISa dynamic program overadapted policiesCHECKED BYno algebraic certificateabove: the DP's own greedypolicy — attainablebelow: perfect information— a rigorous relaxationbracket 9.7 % of the effectat σ_L = 0 it returns M4a'scertified value, to 1.8e-06AND THE WORD IT EARNSCONVERGED ANDBRACKETEDthe alpha worldPhase 2 · M5THE ENV CHARGESthe same power law, plus aninvented one-step-aheadprice signal, ρ = 0.01THE REFERENCE ISa dynamic program over(inventory, signal)CHECKED BYno algebraic certificate;at ρ = 0 it returns M4a'scertified valueimpact and risk are convexand carry no signal, so nopolicy can beat M4a'scertified optimum on them— a CERTIFIED FLOOR, so thered flag is a proofAND THE WORD IT EARNSCONVERGED, WITH ACERTIFIED FLOOR
Each world is graded against a different kind of object, and the word each one earns is part of the claim. Certified was bought with a Cholesky factorisation and a 1.2e-15 KKT residual; a stochastic dynamic program has no such object, so M4b does not reuse the word anywhere it reports the number, and the artefact writes "certified": falsein the field a reader would look for it in. What the bracket buys instead is a red-flag test that is rigorous rather than algebraic: perfect information beats any policy on every path, so the hard failure is a count with no confidence interval in it. M5’s reference is a dynamic program too and takes the same weaker word — but its objective splits, and the half the signal cannot touch is convex, so M4a’s certified optimum is a rigorous floor under exactly the part the red flag is read on. Hence the fourth word in two colours: converged overall, with a certified bound inside it. That is the stronger test of the two, and it is not vacuous — its closest approach across twenty graded policies was 0.0568 bps, which is 84 % of the premium the converged optimum itself pays.

What the agent actually earned

All three Phase-2 results, with their denominators beside them, because the repo's own rule is that the fraction never travels alone — and by the third one a single fraction is not enough either.

M4a — the power-law world

Almgren–Chriss has no closed form for a 0.6-power impact law, so the vendored library linearises at the tangent and solves the linear problem instead. Make the power law the actual world and that schedule is now solving the wrong problem — by 1.54 % of the objective at the reference case, 2.50 % of expected cost, 0.03674 bps either way, which in trajectory space is 16,878 shares — 16.9 % of the parent order. Ten seeds captured a median 99.4 % of that advantage, IQR 0.2 %, worst seed 99.0 %: a median absolute excess over the certified optimum of +0.00021 bps, and a median trajectory distance of 727 shares against a derived local band of 4,739 — 23× closer to the optimum than the closed form is. The red-flag test was green on every seed.

Two things the milestone establishes about the machinery rather than the agent. Phase 1 reproduces bitwise through the new environment seam, so every M2 and M3 number still regenerates from code that exists. And the four guarantees the new world inherits were checked before training rather than argued: they came back green because the power law replaces a term that carries no shock — and they earned their place immediately by catching an antithetic mirror that had quietly rebuilt itself without the primary's impact model and was charging the Phase-1 world. The rewards still looked like rewards and the schedules were still identical; only a per-step identity could see it. Catching it cost minutes instead of a night.

M4b — the stochastic-liquidity world, where the control is the claim

M4b is the first advantage in the project that no fixed schedule can capture at all. Under the power law alone the closed form was merely solving the wrong problem, and a different fixed schedule fixed it; here liquidity becomes a second, independent noise source, the agent gets to see it, and the best possible fixed schedule is beaten by a policy that reacts.

Which makes the denominator the thing to watch again, and it moved again. Three rungs, all closed forms bar the last:

RungWhat it knowsCost
M4a's scheduleNo liquidity at all — the power-law optimum solved in the world without it.2.49895 bps
J_static*The liquidity law, but not its realisations — the best possible fixed schedule.2.49661 bps
J_DPThe realised liquidity, bin by bin — the optimum over adapted policies.2.43449 bps

The milestone's denominator is the adaptive advantage — the distance between the best fixed schedule and the best adapted policy, 0.06212 bps, 2.55 % of the objective. Deliberately not the distance from M4a's schedule: 0.00234 bps of that, 3.8 % of the advantage, is a level shift that any static solver picks up for free by re-solving at an inflated coefficient, and claiming it would be claiming credit for arithmetic. Both rungs are computed in closed form rather than simulated, because differencing two simulated levels would put a 0.002 bps quantity under a per-path standard deviation of 0.18.

Ten seeds captured a median 99.0 % of the adaptive advantage — IQR 0.013, worst seed 96.3 % — a median excess over the adapted optimum of +0.00064 bps. The grade is an average rather than a closed form, and the estimator is built so that the level is never estimated directly: the per-path standard deviation of the conditional expectation is 0.18 bps against an effect of 0.062, so every policy is scored as a difference against the static optimum on the same paths — unbiased, because the static schedule's expected conditional cost is exactly its closed form. Measured, that pairing takes the standard deviation from 0.178 to 0.062, a variance reduction of 8.2×, for a 95 % half-width of about 1.4 % of the effect at 20,000 held-out paths.

And then the control, which is the part of the claim that does the work. Re-graded with the observed liquidity drawn independently of the liquidity actually charged, the same policies score a median capture of −1.01, against a bar of 0.15 stated before the run. That is not merely a failure to capture the advantage: it puts them roughly one whole advantage-unit worse than the best static schedule. Reacting to a decorrelated signal costs almost exactly what reacting to the true signal gains, and the gap between +0.99 and −1.01 is what says the agent is using the signal rather than having found a better fixed schedule.

// the number line — M4b, to scale
the agentmedian 99.0 % of the advantage+0.00064 bps over J_DP · ten seedsIQR 0.013 · worst seed 96.3 %the shuffled controlthe same policies, re-graded with theobserved liquidity drawn independentlyof the liquidity actually chargedmedian capture −1.01against a bar of 0.15, stated before the runM4a’s schedule0.99 captured1.01 given backJ_DP2.43449 bps — the optimum overadapted policies. Converged andbracketed — never certified.J_static*2.49661 bps — the best fixed schedulethat knows the liquidity law. Nothingwithout sight can beat it.M4a’s schedule sits 0.00234 bps pastit, at 2.49895 — a level shift, 3.8 % ofthe advantage, free to any static solver.the axis is cost in bps, further right is worse · one advantage-unit =J_static* − J_DP = 0.06212 bps = 2.55 % of the objective, at an invented σ_L of 0.5
Every rung of M4b on one axis, spaced true to scale. The agent’s mark sits 0.00064 bps to the right of J_DP— three pixels at this width, which is the result — and M4a’s schedule sits a level shift beyond J_static*, which is why the denominator is the adaptive advantage rather than the whole distance. The mark that turns the rest into evidence is the one on the right. Re-graded with the observed liquidity drawn independently of the liquidity charged, the same policies land almost exactly as far the wrong side of the best fixed schedule as they had landed the right side of it: reacting to a decorrelated signal gives back what reacting to the true one earned.

One more thing did not change, and the milestone is careful to say so: the objective. Liquidity divides the participation rate and nothing else, so it is not a new cost encoding; the graded variance is still price-shortfall variance, liquidity enters expected cost through Jensen and never through the risk term, and invariant 7 needed no amendment. The realised-cost variance the differential measures now has two sources while the graded variance has one — and that distinction is checked rather than left in prose.

M5 — the alpha world, where the headline cannot be read on its own

M4a's advantage came from a better fixed schedule. M4b's came from reacting to something no schedule could know. M5's comes from predicting the price — and the prediction has to be paid for, which is what makes it the first milestone in the project whose headline is not a number. Tilting a schedule to monetise a signal costs execution quality, and the converged optimum itself gives back 45.5 % of the gross effect in order to do it. So a single capture fraction cannot tell a policy that traded the signal well from one that traded it badly and executed well, and the §9 entry An advantage that is a difference of larger numbers is graded in its parts, and a level does not carry the same guarantees as a difference makes three numbers the headline — each with its own bar fixed in advance, each carrying its absolute bps, and none of them emittable without the others.

FractionAbsoluteBar
Alpha capture+0.9767+0.14463 of 0.14808 bps gross≥ 0.85
Execution premium1.0480×+0.07055 of the 0.06732 bps the optimum pays≤ 1.30×
Net capture+0.9341+0.00532 bps over J_DP, on an advantage of 0.08076 bps≥ 0.90

Worst seed 0.8925 on net capture, IQR 0.037, and all ten seeds trained their full 751 updates with none near the runaway guard. The denominator is the net advantage — what the converged dynamic program gains over M4a's certified optimum in the same world — and deliberately not the gross alpha, which would have read 1.83× larger in the flattering direction. The signal is one parameter and it is Temper's, not FrontierView's: a one-step-ahead draw in the observation at rho = 0.01, so it explains one part in ten thousand of the next bin's return variance. FrontierView vendored an impact law and no signal, so §7's "vendored, not invented" cover does not reach this milestone either — and the result is therefore a curve rather than a point: the advantage is 0.0057 bps at rho = 0.0025, 0.0808 at 0.01 and 8.89 at 0.2, while the share the optimum gives back falls from 49 % to 20 % across it. A bigger signal is worth proportionally more, not merely more.

The control is the claim here too, and it is the same shape as M4b's. Re-graded with the observed signal shuffled — the same policies, the same price paths, a signal about somebody else's shocks — net capture goes to a median −0.87, worst −0.70, against a bar of −0.50 stated before the run. The gap between +0.93 and −0.87 is what says the agent trades the signal rather than having found a better fixed schedule: a policy tilting on an unrelated signal pays the premium and monetises nothing, which is exactly where the control lands.

One number in the decomposition is an artefact, and it is drawn rather than smoothed away. The graded M4a schedule monetises no alpha by construction and ought to read an alpha capture of zero; it reads −0.0035, because the 200,000 shared signal paths have a non-zero empirical mean at the 1/√M scale. It cancels exactly out of net capture, which is a paired difference against that same schedule, and not at all out of alpha capture, which is a level — the second half of that §9 entry, and the sharper half. A decomposition is reported precisely so that its parts get read, so a fifth of a percent of a reported number stays visible.

And the thing both phases turned out to be about

The most portable result the project produced is in none of the tables above, and it exists only because its two ends were measured three milestones apart. M2 found that on this case, at a per-episode signal-to-noise ratio of roughly 1:70, rediscovering a closed form from the realised reward is a lottery — that is the recorded miss committed beside the pass, and it is why the control variate and then the antithetic pair exist at all. M5 hands the same agent, at the same configuration and the same budget, a signal seven hundred times weaker, on a reward whose conditional variance is exactly zero — and it captures 93 % of what is there. Same optimiser, opposite outcomes: it fails on a large signal under noise and succeeds on a tiny one without it. The §9 entry is The binding constraint across Phase 1 and Phase 2 was the estimator's variance, not the signal's strength, and the axis separating the two results is the reward's variance rather than the effect's size.

That zero is structural rather than lucky, and the pairing reversal below is what bought it: the training reward is the grading formula, agreeing with it to 2.5e-12 bps, with no price sampling anywhere in the milestone. Measured at the pair during the acceptance run, the sampled half carries around 3,000 bps² of return variance per update against 0.111 averaged — a ratio of 3.76e-05, where M4b's comparable figure was 3.269e-02 and irreducibly non-zero, because liquidity noise is not something a pairing can remove. What that bought is not a better chance of success. It is that a failure would have meant something: with the conditional variance at zero, a low capture fraction could not have been ambiguous between "PPO cannot find this signal" and "the estimator cannot show it to PPO". It would have been a finding about PPO and a one-part-in-ten-thousand signal, to be reported as one rather than debugged. Designing an estimator so that failure is interpretable is worth more than designing it so that success is likely.

It also ran on a wire

Phase 3 is done. The trained policy — M4a's median seed by a committed rank rule, exported as a plain .npz and run through a numpy forward pass with no training stack on the client's import path — worked a 1,000-share parent order over thirteen bins on a live Anvil book, as that protocol's third independent client after the browser UI and DepthCharge, with zero changes to Anvil. Every fill was predicted in closed form before anything was sent, and three ladder shapes came back matching level for level — 11.21 bps on the reference book, 22.85 on a deliberately thin one, 33.63 on a wide-spread one, each to the digit and each reproduced across two revisions. The run against the public deployment filled 1,000 of 1,000, attributed every share, and reported the measurement void on 236 third-party fills, which is the milestone's own pre-stated condition firing for the healthiest reason available.

That story is told in full on the project page, and the reason it belongs there rather than here is the one sentence in the README that matters for this page's argument: no ε, no capture fraction and no oracle comparison appears beside an Anvil number — none of them is defined on that venue. The constitution decided that before the client existed: the live leg is a demonstration that the policy speaks a versioned venue wire end to end, and performance claims stay in the simulator. A page about grading has exactly one thing to say about the wire run, and it is that the grading vocabulary was deliberately not carried onto it.

How the project changes its own rules

Everything above depends on a constitution that can be amended without becoming untrustworthy, and §9 of ARCHITECTURE.md is the mechanism. It is an append-only log of structural decisions about Temper, each with what changed and why, and the sections it amends point back at it: §4's environment contract and §5's training section both carry "amended by" bullets that name their entries and instruct the next session to read them before implementing. Portable practice — a rule that would apply just as well to Anvil or Crucible — leaves the constitution entirely and lands in docs/house-notes.md instead.

Cite entries by title, never by date. That is §9's own instruction and this page obeys it. The date column records the session date rather than a commit date, and several entries legitimately share one — M0, M1 and M1a all closed on the same day and five entries carry it, and all six of M5's carry 2026-08-25. Titles are unique; dates are not. A session reconciling the table against the git log will find more entries on a day than that day has commits, which is the sessions and the commits keeping different time rather than drift to be corrected.

There are twenty-nine amendments, six of them M5's. Ten are set out below in full, chosen because they are the ones that changed how a number is allowed to be produced or reported; the rest follow in a table. The three M5 entries in full are there for a reason that is visible once they are read together: each one is the named successor to an M4 entry already in the list, so what the last three cards show is not new material but the same three rules at a higher resolution.

M0The oracle carries two Almgren–Chriss decay rates, not one.

WHAT CHANGEDOne function reproduces FrontierView's κ and is what the vendored goldens pin; a second solves the discrete stationarity condition for the frozen objective, and is what M2 onward grades the agent against. The constitution's "the optimum of this objective is the deterministic AC sinh trajectory" is unchanged and still exact — it now names the second one specifically.

WHYFrontierView's κ is a continuum-limit expression that drops a factor at realistic order sizes, and the two rates differ by about 2.2×. Both trajectories are sinh, so the difference is invisible by eye — and grading rediscovery against the vendored one would have made a correctly trained agent score up to 18 % better than the "optimum". Keeping both satisfies invariant 2, since the oracle still matches the goldens exactly, without making the rediscovery claim unfalsifiable.

M0Phase-1 temporary impact is linear at the tangent η̃, not at η.

WHAT CHANGEDThe constitution's "linear temporary impact" is read as the tangent to FrontierView's power law at the order's own TWAP participation rate. The power-law charge stays in the oracle as the thing the goldens pin; the tangent charge is the frozen objective.

WHYFrontierView's temporary impact is a 0.6-power law, which admits no sinh closed form; it linearises only in order to derive the schedule, and the constitution had assumed a natively linear model. Naming the tangent explicitly is what keeps invariant 7 true and keeps the environment, the reward and the oracle optimising one functional.

M2A deterministic policy on a price-free observation is graded analytically, not by Monte Carlo.

WHAT CHANGEDTwo distinct routes to a number, and which is legitimate depends on what is being tested. Sampling is right for M1, whose subject is the simulator; the exact route — one deterministic rollout, then the closed form on the schedule it realised — is the only route by which an agent may be graded from M2 onward. Validity is asserted rather than assumed: the policy is rolled out on two unrelated shock streams and the trajectories must be bitwise equal.

WHYThe Phase-1 observation carries no price and inventory evolves purely from actions, so a deterministic policy induces an open-loop schedule whose moments are a closed form. That is not a convenience: at M2's case the objective is ~2.4 bps against a per-episode cost SD of ~95, so resolving ε by sampling needs on the order of 107 episodes per seed and would still report an interval where the analytic route reports a number. The bitwise assertion is what makes the shortcut sound — it fails loudly the moment price reaches the observation, which is also the moment analytic grading would silently start lying.

M4aA metric grades the world that charges it.

WHAT CHANGEDThe metric registries are keyed by cost encoding, the environment republishes the functional it charges from its injected impact model, a reference row carries the world its optimum was solved in, and the grader checks the two agree before computing anything. It supersedes a flat allow-list of one encoding — and generalises the quarantine rather than lifting it, so a Phase-1 result still quotes the vendored number beside its own while the grading path still has no route to it.

WHYThe flat rule was the right rule stated in the only way that was checkable while one world existed. M4a makes the power law a world, so the ban would have had to be bypassed — and a bypassed check is not a check. The replacement is strictly stronger and concretely so: the flat rule could never have caught a linear metric grading a power-law environment, because linear was permitted to everything equally, and that is now the live failure mode. The check that was wide enough to be free was also wide enough to be blind.

M4aThe tolerance's denominator is the available advantage, not the TWAP gap, wherever the closed form is the thing being beaten.

WHAT CHANGEDA tolerance now names the denominator it is stated in — the TWAP gap for M2 and M3, the available advantage for M4a — and the verdict is read on the matching field, so a config has to state which question it is answering. M4a's headline becomes the capture fraction, with the absolute excess in bps travelling beside it everywhere: in the results file, the driver's output, the figure caption and every assertion.

WHYAt M4a's λ the whole available advantage is 0.03674 bps, while 5 % of that λ's TWAP gap is 0.06628 bps as M3 computed it and 0.07433 bps re-derived in the power-law encoding — 1.8× to 2.0× the entire effect. An agent held to the old ε would have passed while capturing none of the mis-specification. Two things generalise: a portable denominator is only portable within the question it was written for, and the direction of the reporting trap flips with the denominator — a capture fraction near 1 on an advantage of 0.037 bps is a small absolute claim, and the fraction alone would make it sound like a large one.

M4bA numerical reference is bracketed, not certified, and the red-flag test moves to the bound that is rigorous.

WHAT CHANGEDThe dynamic program is the first thing in the repo graded against and not certified, and the word is absent everywhere it is reported — the object writes "certified": false and the results file names its reference kind. What it has instead is two-sided: the DP's own greedy policy is a real policy and therefore a feasible upper bound, and M4a's Newton system generalised to per-bin coefficients and batched over paths is a perfect-information relaxation and a rigorous lower one. Measured, the bracket is 9.7 % of the adaptive advantage.

WHYM4a earned the word with a Cholesky factorisation, a 1.2e-15 KKT residual and an independent solver; a stochastic dynamic program has no such object, and reusing the word would have been the first dishonest number in the repo. The bracket is too loose to grade against and exactly tight enough for the thing that matters — it makes the red-flag test rigorous where M4a's rested on an algebraic certificate. It came out sharper than predicted, too: perfect information beats any policy on every path, so the hard failure is a count with no confidence interval in it.

M4bThe antithetic pair holds liquidity common, so action identity survives a richer observation.

WHAT CHANGEDThe variance-reduction mirror is handed the same liquidity path and only the price draws are negated, so both halves see identical observations, take identical actions, and their price noise still cancels exactly. The pair gains a third per-step assertion — that the two halves saw the same liquidity — and what it no longer removes is the liquidity noise, which is the reward variance the agent trains through.

WHYThis narrows an earlier entry for the second time. The predecessor named "a second, independent noise source or a price-bearing observation" as what ends the pairing's exactness; the disjunction is too wide, and the half that is wrong is the useful half. What ends action identity is an observation the two halves disagree about, which is a price-bearing one, not merely a richer one. Measured before a seed was spent: cancellation worst 3.4e-16 bps per step against a 1e-12 bar, still exact, and the noise identity surviving at 2.4e-14 relative.

M5An advantage that is a difference of larger numbers is graded in its parts, and a level does not carry the same guarantees as a difference.

WHAT CHANGEDThe successor to the denominator entry above, which fixed which gap the fraction is over; this fixes how many numbers the fraction has to travel with. At M5's signal strength the converged optimum pays back 45.5 % of the gross alpha in order to monetise it, so the net gain is a difference of two quantities that move together. Three numbers are reported with their own pre-stated bars — alpha capture ≥ 0.85, premium ratio ≤ 1.30×, net capture ≥ 0.90 — the formatter refuses to emit one without the others, every fraction carries its absolute bps, and the verdict gates on every bar the document records rather than an enumerated subset.

WHYOne fraction cannot separate a policy that traded the signal well from one that traded it badly and executed well. The premium fraction turns out to be a property of the world rather than of the risk aversion — 44.95–49.94 % across the seventeen vendor λ, with fourteen of them resolving to 45.0–46.5 % once the grid is refined — which is what makes the entry generalise past this case, and finding it cost a correction: the 49.9 % first recorded was a discretisation artefact of the three λ that had not converged, caught only by a grid-stability check written to run before promotion. The second half is sharper. Net capture is a paired difference and alpha capture is a level, so the 1/√M mean of 200,000 shared signal paths cancels out of one and not the other: a schedule that monetises no alpha by construction records −0.0035 rather than 0. It is a fifth of a percent of a reported number, and it is drawn on the figure rather than smoothed away, because the reason to report a decomposition at all is that its parts are read.

M5The red-flag test moves to the half of the objective the signal cannot touch, and stops being a threshold.

WHAT CHANGEDThe successor to the bracketed-not-certified entry above, and explicitly the opposite move to it: M4b made the red flag rigorous by finding a bound below a numerical reference, M5 by finding a subspace the reference is exact on. The conditional grade splits into impact and risk plus an alpha term; impact and risk are convex in the schedule and contain no signal, so their expectation is at least M4a's Cholesky-certified optimum in the same world for any policy whatever. That floor is carried into every grade, and the flag fires on execution falling below it.

WHYAn agent below the floor is a defect with a proof, not a seed that got unlucky — and nothing about the signal or about the dynamic program's convergence enters the argument. The alternative was M4b's construction transplanted, a perfect-information relaxation on the price path, and the brief said before the code did that it is 1,075× too loose to ever fire at a signal this weak: the test would have been green by construction and would have certified nothing. The convexity floor is tight instead. Across ten seeds and ten shuffled controls the closest approach was 1.876355 bps against a floor of 1.819586 — a margin of 0.0568 bps, 84.3 % of the premium the converged optimum itself pays. A test whose margin is a fraction of the quantity being measured is a test that could have fired, so zero red flags across twenty graded policies is a measurement rather than a tautology.

M5The antithetic pair shares the signal and negates only the unpredictable half of the price, so its average IS E[cost | s] — and action identity does not end after all.

WHAT CHANGEDThe mirror is handed the primary's signal stream unchanged; only the price generator is negated. The shock identity generalises rather than retiring — the two halves' shocks average to the conditional mean, which at zero signal strength is the exact negation and so is M3's assertion verbatim in every earlier world. This supersedes a prediction written in M5's own brief, that the pair's action identity would end because the halves see signals they disagree about, and it supersedes the arrangement M5's task 3 built from that prediction.

WHYThe brief's conclusion was right and its mechanism was not, and the difference decided whether the milestone could train at all. The reasoning assumed each half acts on its own observation; this pairing hands one action to both, so negating the signal negates the whole shock and cancels the predictable half along with the unpredictable one. Measured over 1,500 episodes at a strong signal, that arrangement gives an averaged reward with a standard deviation of exactly zero across signal paths and a correlation with the conditional grade of 0.027 — an agent trained on it has no reason to tilt, and the acceptance run would have read as a training failure. Sharing the signal gives correlation 1.000000000 and a worst deviation of 2.5e-12 bps: the training reward and the grading formula become the same object. The general lesson is about which exactness to keep. "The mirror's shock is the exact negation" looked like the invariant because it had been one for three milestones; it was a consequence of the shock being pure noise, and the two coincide exactly at zero signal strength, which is why nothing before M5 could have told them apart. The rejected arrangement stays in the tree, used by no production path, because a rejection the suite re-runs is worth more than a paragraph.

The last two of those ten are the amendment mechanism visibly working rather than described, and the second is the more interesting of the pair because it stops narrowing and starts reversing. M3's original entry said the antithetic pairing's exactness would not survive Phase 2 at all; M4a narrowed that to "a second independent noise source or a price-bearing observation"; M4b narrowed it again and found the surviving disjunction still too wide, the half that was wrong being the useful half. M5 then took the remaining half — the price-bearing one — and overturned it: action identity does not end after all, because the property worth keeping was never that the mirror's shock is the exact negation. That was only ever a consequence of the shock being pure noise. A log where entries only ever accumulate is a changelog. A log where a later entry can say "the entry above is too wide, and here is the measurement" is a constitution — and one where an entry can say "the entry above is backwards, and the rejected arrangement is still in the tree so the suite can re-run the comparison" is a constitution that is being used.

The other nineteen

Summarised, one line each. The full text of every entry, with the reasoning and the measurements behind it, is in §9 of ARCHITECTURE.md.

EntryWhat it settles
The canonical grid is FrontierView’s, in hours.6.5 trading hours, half-hour bins, thirteen of them; costs in bps of notional and variance in bps². Recorded first because it is the first thing every later milestone needs.
The shock lands before the bin executes, so the shortfall variance sums inventory before each bin.Pins an index convention that is invisible in code and worth 20.6 % of the variance for TWAP at thirteen bins — and gives the frontier a hard floor rather than letting risk vanish at instantaneous execution.
Phase 1 is the linearised world end-to-end; the power-law charge is reporting context only.The two charges differ by 12–54 % of expected cost on the golden parameter sets, so splitting the reward from the metric across that gap would have violated invariant 7 outright.
Phase-1 rediscovery trains on the noise-free reward; sampled rewards do not resolve the objective.The claim weakens from "RL under noise recovers AC" to "RL optimises a deterministic function and recovers AC", and every result carries the weaker sentence verbatim. On sampled rewards five seeds scored 0.066, 0.009, 0.819, 0.098 and 0.147 of the TWAP gap — a lottery, not a plateau.
matplotlib is pinned and confined to one module.The confinement is the decision rather than the library: a later session may swap the library and must not swap the boundary. A repo-invariant test enforces the one-file allow-list and the backend ordering.
A result is produced from a committed tree or it is not an acceptance artefact.The dirty-tree flag ignores results/ and is stamped at the start of a run rather than the end. Relatedly, torch's intra-op thread count becomes a committed hyperparameter — the same seed scored 0.165 and 0.066 on four threads versus eight.
Invariant 5 is enforced per module, not by a blanket ban.The flat rule could never have caught the failure the invariant is actually about — evaluation grading on a stream it trained on — because both pools were forbidden to everyone equally.
Antithetic pairing is the Phase-1 variance-reduction regime, and at this reward magnitude it is bitwise the control variate.Every episode runs against the shock path and its exact negation. Validated before use: median gap fraction 0.000168 against a gate of 0.002, per-update reward variance 3,377 bps² sampled against 3.4e-08 averaged, and the trained policies agreeing bitwise with the previous estimator's.
A sweep point’s λ is verified against a pre-stated sub-grid; only a single-point experiment is verified against the selection rule.A frontier must visit nine λ, so the one-λ rule would have had to be bypassed — and a bypassed check is not a check. The sub-grid must contain the rule-selected point, so every sweep carries a point directly comparable to a committed result.
The per-λ tolerance is meaningful only where the testbed is discriminative, and the frontier measures where that stops being true.The predecessor to the denominator entry above, pointing the other way: a gap fraction alone makes a healthy agent look as though it degrades at low λ, when what has moved is the quantity it is divided by.
The power law’s break is in a shock-free term, so the noise identity and the antithetic pairing survive it exactly.Temporary impact is a function of the schedule and carries no shock, so realised cost is still affine in the draws. Measured before training — and the check caught a mirror environment that had silently defaulted to the Phase-1 world on its first run.
The oracle stays independent, and the question is closed rather than carried.Reopened as promised at M3 and closed with the measurement: the shared surface is now about 5 % of a 1,080-line module, so a dependency would save one line in twenty while destroying the property the other nineteen exist to provide.
A trained policy is a committed artefact, selected by rule and verified like every other.Through M4a every network was discarded the moment it had been graded. The export retrains one named seed of a committed sweep, chosen by a rule that takes the worse of the two central ranks so the tie-break can only cost, and writes plain numpy arrays with no pickle on the read path.
A liquidity-observing policy is graded by conditional expectation, not by sampling realised cost.What retires is the open-loop shortcut; what does not retire is the assertion that licensed it. The price still never enters the decision, so conditioning on the liquidity path removes all of the price randomness analytically — there is no price sampling anywhere.
A second noise source gets its own seed pool, or the first one moves.The failure this prevents would have been completely silent: a liquidity draw taken from the price generator shifts every downstream shock, and every committed result would still have regenerated perfectly, against a different market. Acceptance is arithmetic — one M3 seed and one M4a seed retrained bitwise through the new seam.
Liquidity is not a cost encoding, so the λ rule is applied to a third reading and the reading is recorded.The charge is unchanged; what M4b randomises is the market. The two candidate readings disagree — one selects the λ that agrees with M3 and M4a, the other clears its bar by 0.011 percentage points — so the rejected reading is recorded with its margin rather than quietly dropped.
A price-bearing observation is permitted when the shock it predicts is not yet committed — and “committed” is a bin earlier than “landed”.The observation-minimality guard has refused anything moving with the price draw since M1a; M5 moves its line for the first time, into two clauses rather than one. The brief's own wording for the amendment was wrong by one bin, and the hole that would have left is one bin per decision — invisible in every number the milestone reports, because a seam pointed one bin short simply returns a smaller advantage with every gate still green.
A conditional grade’s conditioning set must equal the policy’s observation set, and conditioning changes the variance the differential standardises against.Every conditional grade since M4b rested on a reason argued in prose, and prose is not a check. The two failure directions are opposite and both silent — biased in the direction of the policy's own cleverness one way, an interval that collapses the other — so both are exercised as refusals before a number exists.
There are three injected seams, and the third is the one where inheriting by omission falsifies the record rather than changing a number.§4 said two, and was right the day it was written. A Phase-2 world inherited by omission changes a number; a predictive observation inherited by omission would make every result before M5 a claim about a market in which the agent could see one step ahead — and each would still regenerate perfectly from its own config. So the count is now read off the environment’s own seam objects rather than asserted in prose, and the constitution gains a bullet rather than losing a sentence: a count in a normative section is superseded by title, never revised in place.
The binding constraint across Phase 1 and Phase 2 was the estimator’s variance, not the signal’s strength.M2's 1:70 lottery beside M5's 93 % capture on a signal seven hundred times weaker, from the same agent at the same budget on a reward with zero conditional variance. What it changes is what a null result would have meant: designing an estimator so that failure is interpretable is worth more than designing it so success is likely.

And the practices that outgrew the project

Practices that generalise past Temper leave §9 entirely and land in docs/house-notes.md, where they are cited by title the same way. The most consequential of them is the most portable thing the project has produced about how to work — the finding two sections above is the most portable thing it produced about what it found, and they are different kinds of object. This one is called No code path may be reachable only at the end of a long run, and it asks for one property: every path that runs after an expensive producer — the JSON assembly, the verdict, the caption, the figure, the line that prints a grade, the line that says where a file was written — must be exercisable on fabricated data, without running the producer, in a test that takes milliseconds.

It inverts an instinct, and the note is explicit about which one. The natural ranking puts model and training code first and reporting code last, because the training loop is where the hard thinking is and the reporter is just printing. The ranking is backwards, and the reason is when each one fails. A defect in training code fails in minutes — the first update, the first batch, the first assertion — and costs a re-launch. A defect in reporting code fails after the run, when every number has been computed and nothing has been written, and it costs the run.

Test-criticality is not proportional to how clever a line is; it is proportional to how much work is already sunk when the line first executes. By that measure the last line of a driver is the most test-critical line in the repo, and the loss function is among the least.
docs/house-notes.md · No code path may be reachable only at the end of a long run

M4a paid two hours to learn it. The verdict block was edited to read its pass/fail bar off a world-dependent field; the edit dropped the one line computing the red-flag count, and the suite stayed green — because every test that reached the document assembly got there by training first, so not one of them had ever run it. Ten seeds trained for two hours, were graded correctly, and the driver then died assembling the file. Nothing was written, and the only reason it cost two hours rather than two evenings is that the pipeline is deterministic and the re-run reproduced every seed exactly.

What happened next is why the note had to be restated. It was originally titled after the function it was about, and M4b cited it and obeyed it exactly where it was named — that writer's new keys were covered on fabricated data before the run, and the coverage held. The same defect class then arrived four more times in reporting code the title did not name: a grade shape misread after the first seed had trained; a dry run printing the deterministic world's advantage as the bar, understating a pre-stated threshold by 1.7× in the flattering direction; a figure tool that died reporting where it had just written the figure; and, after all ten seeds had been graded, the closing summary reading a key the new world's summary does not have. That last one is the M4a defect exactly — one milestone later, in the same driver, forty metres down the same function, in a session that had explicitly set out to obey this note. Naming the writer had made the writer safe and had done nothing for the twenty lines under it.

So the title became the property, and the check that follows from it is mechanical, which is what makes it a rule rather than a resolution: take the artefact your producer writes, hand it to every function that runs after the producer, and require them to complete. If a function cannot be called that way, that is the finding — extract it until it can. Here that is twenty-nine tests running in seconds with no training anywhere, and each of the four defects above is now one of them. ROADMAP.md handed the rule to M5 as a definition-of-done item rather than as advice, and that is where the note's most useful measurement comes from — because this time the pre-run pass existed, ran before the first seed trained, called every function the brief named, and reported green. It caught none of M5's three defects, and each miss was structural rather than unlucky.

Two of the three were in main — an unbound name in an --expect check, and a closing line reading a key the alpha world's summary does not have, so the report came out quietly smaller than the run. A pass is a function that calls functions, and main is not one of them, so no amount of coverage below it could have reached either. The third the pass did call, ten times, on ten identical fabricated grades — where the maximum and the minimum are the same number and a direction error is invisible by construction. That one was a wrong number in a shipped artefact: the summariser calls worst the maximum, which is right for a cost and backwards for a capture fraction, so the file reported the sweep's best seed as its worst, at more alpha than the optimum has to give. It is the only defect in the note that never fired at all. The others announced themselves by crashing; a wrong number does not crash, it gets quoted.

So the note gains two clauses that the rule needed and did not have. The fabricated data must vary along every axis the path computes over — constant input proves the imports resolved and the shapes line up, and says nothing about what the path computes — with a preference for a case whose right answer is known independently, one seed worse on every axis at once, so that "which is worst" is established by construction rather than by the code under test. And the entry point is a path too: a rule written over functions stops at the last function, and argument handling, exit codes and the lines that print where things were written all live below it. The first attempt at the second clause is worth more than the clause. A check was written that read the module's own direction table and asked whether the document agreed with it; reintroducing the defect made the test pass, because the table and the document were now agreeing with each other about a wrong answer. The oracle for a test must not be the thing under test — which is invariant 2 again, one level down, and the reason this project has an oracle at all.

And the newest, about a check that was reached by nobody

M5 earned a fifth note on the same day, by a route the fourth one does not cover. A guard that takes its context as an argument is only as strong as its call sites is about a check that exists, is correct, and is not reached. Experiment.denominator_bps refuses to hand back M4a’s tangent advantage for an alpha-aware config — in that world the tolerance is a fraction of the net signal advantage, and the deterministic row returns a materially smaller one. The refusal is right, and it fires. It fires when the function is called with no row at all, which is the one case no caller was ever going to produce.

Both of the driver’s banner paths printed the wrong bar anyway, and they reached it by the two different routes a call site has available. --dry-run called the guard and handed it a row, so the condition the refusal hangs off was false, the raise never evaluated, and the deterministic advantage came straight back. _header — the banner the four-and-a-half-hour acceptance run printed — never called the guard at all: it read the field off the reference directly, which is the same field the guard exists to withhold. One caller went round the check; the other went round the function. Test the callers, not the guard — because a test that constructs a good argument and checks the refusal exercises the guard and nothing else, and it passes on both of those days.

Any function whose refusal depends on context has this shape — a permission check taking a role, a validator taking a schema, a formatter taking a locale, a sanitiser taking an output encoding. Such a guard is cheap to test and its test proves almost nothing about the system: every failure mode is a wrong argument arriving from somewhere else.
docs/house-notes.md · A guard that takes its context as an argument is only as strong as its call sites

The cost is measurable rather than hypothetical, which is what earns it a note. The banner printed an ε of 0.00367 bps where the milestone’s bar is 0.00808, and the run’s own median excess over the reference came in at 0.00532 — which fails the printed bar and passes the real one. For the length of the run the banner and the artefact disagreed about the verdict on the milestone’s headline gate. The artefact was right throughout, because every graded number is computed from the row the grader resolves rather than the one the banner quotes, and the committed file records the gate as met for the correct reason. Only the thing a human read was wrong — which is the good version of this failure, and still worth a note, because the banner is the thing a person watches for four and a half hours.

How the branch got there untested is the half worth keeping. The liquidity world’s branch of this same guard has been asserted since M4b. M5’s branch was added by analogy with it, in the same commit, with a comment transposing M4b’s argument into M5’s numbers — and the analogy carried the implementation and not the test. Nothing asserted the new branch, so nothing was in a position to notice that both callers were routing around it. What replaces it is five tests that read the banners the way a reader gets them, and the load-bearing assertion among them is the negative one: the deterministic field and the string 0.00367 are required to be absent from the output. A caller test that checked only the good line would have passed against the old code, because the old code printed a perfectly well-formed line carrying a number about a different milestone.

What this does and does not establish

This section is not a disclaimer at the bottom in small type. It is the section that makes the eight above it credible, and the repo treats it that way — the README calls it the honesty ladder and it was written while the limits were fresh, as a task inside M3 rather than as an afterthought. Each rung is a claim the repo can back with a committed config, a committed result and a green suite, and each is deliberately narrower than it might sound.

  • Phase 1 establishes that the pipeline works. The agent recovers Almgren–Chriss in a world where Almgren–Chriss is provably optimal. That is a statement about the optimiser, the environment and the grading path agreeing with a closed form — not a statement about trading. The world is arithmetic Brownian motion with linear impact, the observation is time-left and inventory-left, and the reward the headline agents train on is variance-reduced. On the realised reward, at this case's roughly 1:70 per-episode signal-to-noise ratio, the same agent misses the bar as a lottery — and that miss is committed beside the pass rather than quietly dropped.
  • M4a establishes that the agent finds the optimum of a world whose closed form is derived at a tangent. The mis-specification is real and it is small. It says the agent adapts to a model change the formula cannot. It says nothing about real fills, and 0.037 bps is a small absolute claim that should read as one.
  • M4b's liquidity model is Temper's own invention, and the claim is only ever about it. FrontierView has no liquidity process, so the constitution's "vendored, not invented" cover does not reach here. The model is a per-bin independent lognormal multiplier with unit mean and one parameter, and that parameter is invented — which is why the result is reported as a curve rather than a point: the value of sight is 0.01474 bps at σ_L = 0.25, 0.06212 at 0.5 and 0.15223 at 0.75. The stated claim is exactly this and no wider: with a one-parameter invented liquidity process, seeing liquidity is worth 2.6 % of the objective and the agent captured 99 % of it. Not "the agent adapts to real market liquidity".
  • M5's signal is Temper's own invention too, and it is the smaller of the two claims it sounds like. FrontierView vendored an impact law and no signal, so the same cover does not reach here either. One parameter again, and the result is a curve again — the advantage is 0.0057 bps at rho = 0.0025, 0.0808 at 0.01 and 8.89 at 0.2. The stated claim is exactly this and no wider: with a one-parameter invented one-step-ahead price signal explaining 1e-4 of next-bin return variance, the available advantage is 0.081 bps and ten seeds captured a median 93 % of it, monetising 98 % of the gross alpha while paying 1.05× the optimum's execution premium. Not "the agent trades alpha".
  • Phase 3 is plumbing evidence, not execution-quality evidence. The flow is synthetic and non-adversarial, the sample is one order, and there is no baseline it could fairly be compared against.

None of it establishes real-market performance. That would need real fills, or historical order-book data to replay against, and neither is anywhere in the portfolio. Every number on this page is a statement about a simulator whose dynamics are analytic and whose parameters are FrontierView's calibrated synthetic set. The differential tests are what make it a good simulator; the honest reading of the results is "the machinery is correct", not "the strategy would work". The README notes that this is also the answer to the obvious interview question, and that it is much better volunteered than extracted.

The constitution's own out-of-scope list closes the same way: no buy-side episodes, no multi-asset portfolios, no real historical data beyond the calibrated synthetic parameters, no limit-order placement, no GPU dependence, no venue other than Anvil — and no suggestion of live-capital trading.

What is left, then, is the thing the page has been about. The results are small, bounded and reproducible: a per-commit suite that runs from a clean clone in about two minutes ten, against a three-minute ceiling every brief has stated in advance; no network and no GPU, with a test that enforces both; every reported number regenerating from a committed config and seed with the config's digest and the git revision stamped beside it; every milestone sized to train in an evening on one desktop. The interesting engineering is not the size of the numbers. It is that each one arrives with the denominator it was measured against, the word its reference actually earned, and a written record of the moment the project noticed the bar was wrong.

← BACK TO

Temper