Skip to content

Commit 34dd7c1

Browse files
martex-devclaude
andcommitted
M5: survivorship, the market taxonomy, and a review that kills
The company now overturns its own confirmed result, and nobody intervenes. HYP-0001 CONFIRMED -> REFUTED claimed max_drawdown < 0.20, measured 0.1236 objection SURVIVORSHIP, critical re-run universe 3 names -> 6 names restored LUNC/USDT, FTT/USDT, HOTAIR/USDT max_drawdown 0.1236 -> 0.6451 verdict UPHELD A researcher registers a drawdown claim over the instruments still trading and it is confirmed. A Critic names SURVIVORSHIP. The Chair dispatches the test. The point-in-time re-run restores three delisted names, drawdown goes from 12% to 65%, and the claim dies by measurement rather than by argument. The critic does not write the test ---------------------------------- It names a defect; `meetings/taxonomy.py` generates the varied specification from the one under review. That distinction is the point. A critic that composes its own test can write one that cannot fail, or that tests something else, or that quietly uses a capability it does not hold. Naming a defect gets a test whose construction is written down, reviewed, and identical every time the same defect is alleged. The prose is the critic's; the arithmetic is not. Each builder varies exactly one thing -- asserted by a test -- because a test that changed the universe AND the cost model would settle nothing. Which way "worse" points depends on the metric: drawdown getting bigger is bad, return getting bigger is not, and getting that backwards would make every objection unfalsifiable in one direction and automatic in the other. Survivorship had to be made measurable, not merely discussable -------------------------------------------------------------- The fixture universe gained three instruments that die. The first version had them drifting quietly down -- and survivorship became undetectable, because no ranking rule would ever hold one. That was wrong about how the failure actually happens: LUNA and FTT both looked like the best names right up until they were worth nothing. The casualties now PUMP before they collapse, which is what draws a rotation rule into them, and a universe that quietly drops them reports the attraction without ever paying for it. `point_in_time` is now a first-class engine operation rather than a footnote in a data pipeline, which is the only reason a Critic can see the defect and a test can vary it. The engine went cross-sectional ------------------------------- `rotation` ranks the whole universe each bar and holds the leaders. Multi-asset simulation, still one-bar latency applied in a single place rather than trusted to each signal. Path metrics gained a block bootstrap interval. Without one they cannot support a confirmatory claim at all -- the verdict rule reports UNDERPOWERED, correctly, and every drawdown claim becomes unanswerable. Blocks rather than individual returns because shuffling one at a time destroys the runs of losses that ARE the drawdown. That interval is honestly wide: roughly 20 percentage points around a 200-bar maximum drawdown. So the demonstration declares a minimum effect of 0.11 rather than 0.05. A claim pretending to resolve five points would be UNDERPOWERED and the rule would be right to say so -- declaring what the data can support, before looking at it, is the discipline rather than a concession to it. On the numbers -------------- martex-quant found this defect on real crypto history, where it took a Sharpe of 1.47 to 0.86. Those figures belong to that corpus and are NOT reproduced here; Aurelis has no market data of its own yet. What is reproduced is the mechanism, on fixture instruments where the bias is present by construction. The figures above are whatever the engine computed. Nothing was tuned until a number came out right. Honest finding: leakguard, purged-cv, calibrate and timeleak are not resolvable from PyPI, the same as martex-quant. The integrity checks M5 needs are implemented natively (`integrity.point_in_time`), and doctor reports what is actually present. 376 tests, ruff clean, mypy strict. Also fixed: two earlier README edits used .replace() without asserting and had silently done nothing, so the front page still described M2. Every doc edit in this commit asserts its anchor first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent cf095e4 commit 34dd7c1

13 files changed

Lines changed: 1756 additions & 128 deletions

File tree

README.md

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ themselves as the evidence justifies it.
1111
[![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](https://www.python.org/downloads/)
1212
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
1313

14-
Status: **M4 complete — research runs, and can conclude it found nothing.**
15-
Preregistration locked by database trigger, verdicts derived by rule, every
16-
number traceable to an artifact. · 2026-09-05
14+
Status: **M5 complete — the company overturns its own confirmed result.**
15+
A Critic names survivorship, the Chair runs the test, the claim dies. No human
16+
in the loop. · 2026-09-05
1717

1818
> Research software. No live trading adapter exists. Nothing here is proven
1919
> profitable. Read [DISCLAIMER.md](DISCLAIMER.md).
@@ -24,37 +24,69 @@ number traceable to an artifact. · 2026-09-05
2424

2525
```bash
2626
pip install -e ".[dev]"
27-
aurelis db init # schema, invariants, the org chart
28-
aurelis agent hire # staff the launch roster
29-
aurelis mission run # open a mission and work it to completion
30-
aurelis doctor
27+
aurelis db init # schema, invariants, the org chart
28+
aurelis agent hire # staff the launch roster
29+
aurelis research review # the demonstration
3130
```
3231

33-
`mission run` opens a mission with a **Kickoff meeting**, plans a project into
34-
three dependent tasks, runs them, and closes with a **Retrospective** that
35-
scores the kickoff's forecasts against what actually happened.
36-
37-
Between the meetings: **INTEL** briefs the crypto desk from measured bars.
38-
**QUANT** reads that briefing, checks it against an independent window it
39-
measured itself, and raises a research question. **LEAD-R** decides whether the
40-
question earns a project. Each waits for the one before because the queue will
41-
not hand out a task whose dependency has not succeeded — there is no
42-
orchestrator.
32+
### The demonstration
4333

4434
```
45-
mission MSN-0001
46-
kickoff MTG-0001 — 5 turns, 0 exchange round(s)
47-
turns 3
48-
progress 3/3 succeeded
49-
retrospective MTG-0006 — 5 turns
50-
calibration AG-0004: Brier 0.2500 over 1 forecast — no better than 50/50
51-
chain chain verified: 141 events, seq 1..141
35+
HYP-0001 CONFIRMED -> REFUTED
36+
37+
claimed max_drawdown < 0.20, measured 0.12364208
38+
universe 3 names (still trading)
39+
objection OBJ-0001 SURVIVORSHIP, critical
40+
test the same rule, universe restored to point-in-time
41+
re-run universe 6 names
42+
restored LUNC/USDT, FTT/USDT, HOTAIR/USDT
43+
max_drawdown 0.12364208 -> 0.64507263
44+
verdict UPHELD
45+
chain chain verified: 68 events, seq 1..68
5246
```
5347

54-
Look around: `aurelis org show` · `aurelis org desks` · `aurelis agent list` ·
55-
`aurelis agent show INTEL` (what one agent holds, sees, writes and may invoke)
56-
· `aurelis mission show MSN-0001` (every task, its status, what it waits on) ·
57-
`aurelis tick` (advance the working day one turn).
48+
A researcher registers a drawdown claim over the instruments still trading,
49+
runs it, and it is **confirmed**. A Critic names `SURVIVORSHIP` — it does not
50+
write the test; the taxonomy generates it from the specification under review.
51+
The Chair dispatches it. The point-in-time re-run restores three delisted names
52+
and drawdown goes from 12% to 65%. The objection is upheld and the claim is
53+
**refuted by a measurement**.
54+
55+
Nobody intervenes at any point.
56+
57+
*martex-quant found this same defect on real crypto history, where it took a
58+
Sharpe of 1.47 to 0.86. Those figures belong to that corpus; the ones above are
59+
what this engine measured on fixture instruments where the bias is present by
60+
construction.*
61+
62+
### The rest of the company
63+
64+
`aurelis mission run` opens a mission with a **Kickoff meeting**, plans a
65+
project into three dependency-sequenced tasks, runs them, and closes with a
66+
**Retrospective** that scores the kickoff's forecasts against what happened.
67+
**INTEL** briefs the desk, **QUANT** checks that briefing against a window it
68+
measured itself, **LEAD-R** decides. There is no orchestrator — a task whose
69+
dependency has not succeeded is simply invisible to the queue.
70+
71+
`aurelis research run` takes a single hypothesis from claim to verdict:
72+
propose, screen for prior art, **lock a preregistration**, design, run, and
73+
derive the verdict from criteria fixed before anything executed. On 240 bars it
74+
returns `UNDERPOWERED`, because 240 bars genuinely cannot detect a Sharpe of
75+
0.05 — and saying so is the point.
76+
77+
Look around:
78+
79+
| | |
80+
|---|---|
81+
| `aurelis org show` · `org desks` · `org charters` | the company as designed |
82+
| `aurelis agent list` · `agent show INTEL` | what one agent holds, sees, writes, may invoke |
83+
| `aurelis mission show MSN-0001` | every task, its status, what it waits on |
84+
| `aurelis meeting show MTG-0001` | the transcript, who changed their mind, who dissented |
85+
| `aurelis meeting calibration` | how good the company's forecasts have been |
86+
| `aurelis research show HYP-0001` | every metric, its interval, and who computed it |
87+
| `aurelis research graveyard` | everything killed, and why |
88+
| `aurelis research defects` | every market defect and how it is settled |
89+
| `aurelis tick` · `aurelis doctor` | advance the working day; check the workspace |
5890

5991
---
6092

@@ -252,7 +284,7 @@ automatically by the company, five milestones in.
252284
| **M2**| Missions | missions → projects → tasks, dependencies, the working day |
253285
| **M3**| **Meetings** | seven-phase protocol, forecasts, objections with tests, dissent |
254286
| **M4**| Research lifecycle | engines, preregistration, experiments, verdicts |
255-
| **M5** | Critique & audit | objections with tests, the H71 reproduction |
287+
| **M5** | Critique & audit | market defects, point-in-time, the review that kills |
256288
| **M6** | Memory & knowledge | graph, lessons, corpus import, vault export |
257289
| **M7** | **Mission Control** | the live facility |
258290
| **M8** | Strategy, portfolio, risk | versions, gates, veto |

docs/07-roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ identical artifact hash. No agent can write a `Result` row.
9494

9595
---
9696

97-
## M5 — Critique, replication, and the Research Review
97+
## M5 — Critique, replication, and the Research Review
9898

9999
- Strategy Lab roles: Critic, Adversarial, Replication, Robustness, Validation.
100100
- Market objection taxonomy with mechanical discriminating tests:

src/aurelis/agents/tools.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,84 @@ def _engine_features(arguments: dict[str, Any]) -> ToolResult:
254254
raise ValueError("features requires a non-empty 'bars' list from data.ohlcv")
255255
measures = describe_bars(bars)
256256
return ToolResult(value=measures, detail=f"{len(measures)} measures over {len(bars)} bars")
257+
258+
@register_tool(
259+
ToolScope.ENGINE_BACKTEST,
260+
"Run an experiment specification and return its metrics as scalars",
261+
)
262+
def _engine_backtest(arguments: dict[str, Any]) -> ToolResult:
263+
"""Run a specification through the engine registry.
264+
265+
This is what makes a discriminating test executable. A Critic alleging
266+
survivorship supplies the same rule over a point-in-time universe, the
267+
Chair dispatches it here, and the room sees the number rather than the
268+
argument.
269+
270+
Metrics come back flat -- ``{"sharpe": "0.12", ...}`` -- so a test can name
271+
a field and compare it without the objection needing to know the artifact
272+
format.
273+
"""
274+
from aurelis.engines.registry import engine_for
275+
from aurelis.engines.spec import ExperimentSpec
276+
from aurelis.research.lifecycle import _spec_from_payload
277+
278+
payload = arguments.get("spec")
279+
if not isinstance(payload, dict):
280+
raise ValueError("engine.backtest requires a 'spec' payload")
281+
282+
spec: ExperimentSpec = _spec_from_payload(payload)
283+
artifact = engine_for(spec).run(spec)
284+
285+
flat: dict[str, Any] = {m.name: str(m.value) for m in artifact.metrics.metrics}
286+
for metric in artifact.metrics.metrics:
287+
if metric.low is not None:
288+
flat[f"{metric.name}_low"] = str(metric.low)
289+
flat[f"{metric.name}_high"] = str(metric.high)
290+
flat["spec_digest"] = artifact.spec_digest
291+
flat["data_fingerprint"] = artifact.data_fingerprint
292+
flat["universe_basis"] = str(artifact.diagnostics.get("universe_basis", ""))
293+
flat["universe_size"] = str(len(artifact.diagnostics.get("universe", [])))
294+
295+
return ToolResult(
296+
value=flat,
297+
detail=(
298+
f"{spec.signal.kind} over {flat['universe_size']} symbols, "
299+
f"{flat['universe_basis']}"
300+
),
301+
)
302+
303+
304+
@register_tool(
305+
ToolScope.INTEGRITY_POINT_IN_TIME,
306+
"Report whether a universe was chosen with hindsight, and what it dropped",
307+
)
308+
def _integrity_point_in_time(arguments: dict[str, Any]) -> ToolResult:
309+
"""Check a universe definition for survivorship exposure.
310+
311+
Deterministic and free. It does not re-run anything -- it answers the
312+
prior question of whether the universe *could* be biased, which is what a
313+
Data Auditor samples for and what tells a Critic whether the objection is
314+
worth raising at all.
315+
"""
316+
from aurelis.engines.universe import resolve_universe
317+
from aurelis.intel.sources import source_for
318+
319+
desk = str(arguments.get("desk", "crypto"))
320+
symbols = tuple(arguments.get("symbols") or ())
321+
point_in_time = bool(arguments.get("point_in_time", False))
322+
323+
source = source_for(desk)
324+
resolved = resolve_universe(
325+
desk, symbols, point_in_time=point_in_time, as_of=source.anchor()
326+
)
327+
return ToolResult(
328+
value={
329+
"point_in_time": str(resolved.point_in_time),
330+
"universe_size": str(len(resolved.symbols)),
331+
"excluded_count": str(len(resolved.excluded)),
332+
"excluded": list(resolved.excluded),
333+
"survivorship_exposed": str(resolved.survivorship_exposed),
334+
"summary": resolved.describe(),
335+
},
336+
detail=resolved.describe(),
337+
)

src/aurelis/cli/research.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,123 @@ def research_engines() -> None:
320320
console.print(f" metrics {', '.join(sorted(capabilities.metrics))}")
321321
console.print(f" desks {', '.join(sorted(capabilities.desks))}")
322322
console.print()
323+
324+
@research_app.command("review")
325+
def research_review(
326+
workspace: WorkspaceOption = None,
327+
bars: Annotated[int, typer.Option(help="Window length in bars.")] = 200,
328+
) -> None:
329+
"""The M5 demonstration: a confirmed claim, challenged and killed.
330+
331+
A researcher registers a drawdown claim over the universe of instruments
332+
still trading, runs it, and it is CONFIRMED. A Critic names SURVIVORSHIP.
333+
The Chair dispatches the generated test -- the same rule over the universe
334+
as it actually stood, delisted names restored. The measurement comes back,
335+
the objection is upheld, and the hypothesis is refuted.
336+
337+
Nobody intervenes at any point.
338+
"""
339+
from aurelis.research.review import hold_research_review
340+
341+
runtime = _runtime(workspace)
342+
try:
343+
runtime.initialise()
344+
runtime.staff()
345+
with runtime.database.session() as session:
346+
quant = runtime.roster.by_handle(session, "QUANT").ref
347+
critic = runtime.roster.by_handle(session, "CRITIC").ref
348+
lead = runtime.roster.by_handle(session, "LEAD-R").ref
349+
outcome = hold_research_review(
350+
session,
351+
research=runtime.research,
352+
chair=runtime.chair,
353+
author=quant,
354+
critic=critic,
355+
chair_ref=runtime.roster.by_handle(session, "OPS").ref,
356+
participants=(quant, critic, lead),
357+
registrar=runtime.roster.by_handle(session, "GOV").ref,
358+
bars=bars,
359+
)
360+
verification = runtime.ledger.verify(session)
361+
finally:
362+
runtime.close()
363+
364+
console.print()
365+
console.print(
366+
f"[bold]{outcome.hypothesis_ref}[/bold] "
367+
f"[green]{outcome.verdict_before.value.upper()}[/green] -> "
368+
f"[red]{outcome.verdict_after.value.upper()}[/red]"
369+
)
370+
console.print()
371+
372+
table = Table(show_header=False, box=None)
373+
table.add_column("", style="bold", width=18)
374+
table.add_column("")
375+
table.add_row("claimed", f"{outcome.metric} < 0.20, measured {outcome.claimed}")
376+
table.add_row("universe", f"{outcome.universe_before} names (still trading)")
377+
table.add_row("objection", f"{outcome.objection_ref} SURVIVORSHIP, critical")
378+
table.add_row("test", "the same rule, universe restored to point-in-time")
379+
table.add_row("re-run universe", f"{outcome.universe_after} names")
380+
table.add_row("restored", ", ".join(outcome.excluded))
381+
table.add_row(
382+
outcome.metric,
383+
f"[green]{outcome.claimed}[/green] -> [red]{outcome.measured}[/red]",
384+
)
385+
table.add_row("verdict", f"[red]{outcome.objection_status.value.upper()}[/red]")
386+
table.add_row(
387+
"chain",
388+
f"[green]{verification.describe()}[/green]"
389+
if verification.ok
390+
else f"[red]{verification.describe()}[/red]",
391+
)
392+
console.print(table)
393+
394+
console.print()
395+
console.print(f" [dim]{escape(outcome.detail)}[/dim]")
396+
console.print()
397+
console.print(
398+
"[dim]The universe was chosen knowing which names survived. A top-1 "
399+
"rotation is drawn to whatever runs hottest, and the names that later "
400+
"delisted ran hottest of all right before they stopped.[/dim]"
401+
)
402+
console.print(
403+
"[dim]martex-quant found this same defect on real crypto history, where "
404+
"it took a Sharpe of 1.47 to 0.86. Those figures belong to that corpus; "
405+
"the ones above are what this engine measured.[/dim]"
406+
)
407+
console.print()
408+
409+
if not outcome.overturned:
410+
console.print("[yellow]The review did not overturn the claim.[/yellow]")
411+
raise typer.Exit(code=1)
412+
console.print(
413+
"[green]M5 acceptance: a confirmed result refuted by a measurement, "
414+
"with no human in the loop.[/green]"
415+
)
416+
417+
418+
@research_app.command("defects")
419+
def research_defects() -> None:
420+
"""The market defects a Critic can allege, and how each is settled."""
421+
from aurelis.meetings.taxonomy import MARKET_DEFECTS
422+
423+
table = Table(show_header=True, header_style="bold", box=None)
424+
table.add_column("defect", style="bold", width=20)
425+
table.add_column("severity", width=10)
426+
table.add_column("varies", width=30)
427+
table.add_column("asks", overflow="fold")
428+
for defect in MARKET_DEFECTS.values():
429+
colour = "red" if defect.severity.value == "critical" else "yellow"
430+
table.add_row(
431+
defect.name,
432+
f"[{colour}]{defect.severity.value}[/{colour}]",
433+
defect.varies,
434+
defect.asks,
435+
)
436+
console.print(table)
437+
console.print()
438+
console.print(
439+
"[dim]A Critic names a defect; the test is generated from the "
440+
"specification under review. The prose is the critic's; the arithmetic "
441+
"is not.[/dim]"
442+
)

src/aurelis/core/enums.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ class EventKind(StrEnum):
119119
chose it."""
120120

121121
REPLICATION_RECORDED = "research.replication_recorded"
122+
VERDICT_OVERTURNED = "research.verdict_overturned"
123+
"""A confirmed result was refuted by a later measurement. Its own kind,
124+
because a corpus that quietly rewrote conclusions would be worse than one
125+
that never revised them."""
122126

123127
# Agent work
124128
TOOL_CALLED = "tool.called"

0 commit comments

Comments
 (0)