Thales
← research journal

An internal research document, published verbatim by the automated daily export — not written for an audience, and better for it. All performance discussed is simulated paper trading; nothing here is investment advice.

RESEARCH_QUEUE.md — the standing research queue

The daily research panel's memory and output (ops/RESEARCH_PANEL.md). Append-only, two sections, one file — deliberately not a dated report per day.

Open = proposed, survived adversarial review, awaiting the owner. Approved = the owner moved it here. This section is the ONLY thing the implementer routine will build — moving an item is the human gate, and it is deliberately one line of effort for the owner and a hard precondition for the machine. Dismissed = considered and rejected, one line each. That section exists so tomorrow's panel does not re-deliberate today's rejects; re-litigating a dismissal without new evidence is the same C0 breach as re-opening a kill.

Nothing here has been actioned. Items become real work only when the owner picks them up; each carries a pre-registered kill criterion so it can die cheaply.

Seeded 2026-08-05, empty, before the panel's first run (2026-08-06 13:00Z).


Open

2026-08-06 · Panel run 1

Seven items, ranked. Three need owner judgement (a policy call only you can make); four are defects with an obvious fix and need only authorisation. The judgement field is there so triage costs seconds.

Composition note, recorded deliberately: none of these is a strategy idea. Zero add a row to results/research_registry.jsonl, zero add DSR debt, zero relitigate the kill list, zero propose changing a frozen pin. Every one is a P0-loudness or P2-instrument defect, which the charter ranks above research. Seven on day one reflects a cold start — the deep panel predates the 08-03/08-05 ops commits, PR #88 had never been reviewed, and the queue-and-clocks lens had never run at all. If a later run produces seven again, suspect the reviewer.


1. MEM-1 — the memory/ directory does not exist in the repo, and the C0 kill list is one of the files that is missing. · judgement: YES · effort 0.25 pd · horizon: immediate

Mechanism. git log --all --diff-filter=A -- 'memory/*' is empty: memory/ was never committed, and it is not in .gitignore either — it lives only on the owner's Mac. Meanwhile 22+ references to 11 distinct memory/*.md files appear across committed docs. Two of them are load-bearing for the autonomous loop: ops/RESEARCH_PANEL.md §2 instructs this panel to check memory/strategic-posture.md (the kill list enforcing the C0 anti-relitigation rule), and ops/WEEKLY_AUDIT.md check #8 instructs comparing "the memory index" against RESEARCH.md/CLAUDE.md — a check that has been reporting green while structurally unable to read one of its three inputs. tests/test_docs_consistency.py verifies that ops/*.md referenced files exist; nothing checks memory/*.md, so the dangling references are invisible to the suite whose stated purpose (line 5) is that "an agent acts on what is written".

Not fatal today, and say so: ops/RESEARCH_PANEL.md §2 enumerates the kill list inline and web/public/data/killlist.json carries 13 entries, so cloud routines are degraded, not blind. This panel used both.

Test design / kill criterion. Extend tests/test_docs_consistency.py to assert every referenced memory/*.md path resolves. It reds today. The owner must then either commit memory/ or strip the references; doing nothing does not satisfy it, and "it works on my machine" is unavailable because CI's checkout is the one that matters. If the owner's intent is that memory/ stay private and local, the correct resolution is to strip the references and inline what the routines need — not to weaken the test.

Owner judgement required: commit vs de-reference is a policy call.


2. CQA-1 — capture_qa cannot fail on a calendar-explained skew day, so a truncated capture file reports OK. · judgement: no · effort 0.25 pd · horizon: immediate

Mechanism. src/thales/data/captures.py, options_skew branch (~500–560). probs accumulates "{bad_lines} unparseable line(s)" before the coverage check. When _skew_window_gap returns an explanation the code appends {"ok": True, …} and sets return_early = True, so the if not return_early: block never runs and probs is discarded entirely. The parallel options_chains branch handles this correctly via _only_explained (captures.py:438) — one of two parallel edits, not a design choice.

Verified, twice. Identical two unparseable lines → ok=False when the coverage drop is unexplained, ok=True when it is explained, with the corruption absent from the detail string. Merged to main 2026-08-03 (f1e3061); thales capture-qa runs daily in skew-snapshot.yml under if: always(), and the heartbeat beacon fires on success() — so a corrupted capture day yields a green job and a healthy beacon. Half-written files are the stated reason this QA exists (module docstring: "staleness alone can't see a half-written or degenerate file"), and calendar-explained days recur on a ~monthly cadence.

Currently latent: the live data/options_skew.jsonl has 0 unparseable lines. This is a hole in the net, not a fish already through it.

Kill criterion. This is a defect, not a hypothesis, so what it needs is an acceptance test plus the charter's mandatory negative control: plant ≥1 unparseable line on a calendar-explained day, assert ok is False and that the detail carries the unparseable count; then revert the fix and assert the test reds. Add the symmetric test on options_chains so the branches cannot drift apart again. No test currently covers bad_lines/_only_explained on either branch.


3. PC-1 — PR #88's "explained" escape has no ceiling, and it fires by construction for meanrev. · judgement: no · effort 0.25 pd · horizon: before #88 merges — free now, expensive after

Mechanism. _check_position_count in execution/health.py on origin/audit/position-count-detector: if cap_bound: return (True, …) with excess unbounded. A book at 5× its targeted name count passes identically to one 3 names over. The PR's own docstring states meanrev's turnover cap "binds by construction" and has fired the identical warning daily since 2026-07-20 — so cap_bound is true daily for that sleeve and the detector becomes structurally silent there. That is the same failure the PR exists to fix ("an alarm that cannot be wrong is an alarm nobody reads"), sign-flipped into an alarm that can never fire. Its 8 new tests cover the unexplained path; none asserts an upper bound on the explained one.

Kill criterion — pin the ceiling BEFORE measuring. "Add an upper bound" invites picking the bound after looking at meanrev's current excess, which fits the ceiling to the behaviour it polices. Pre-registered here instead: excess > expected (book ≥2× the last selection's target) warns regardless of cap_bound, with a test at 3× asserting ok is False. Rationale: a turnover cap can defer exits; it cannot manufacture a book twice its target. Register in the PR description before touching the code.


4. GAP-1 — six permanent capture-days are already lost, and charter gauge G4 claims they are machine-checked when no instrument can see them. · judgement: YES · effort 0.75 pd · horizon: detector kill resolves in 20 td (~2026-09-04)

Mechanism. Verified interior holes inside the pinned clean-clock window:

streamclassmissing trading days
data/processed/portfolio_snapshots.jsonlTIME-GATED2026-06-17, 07-16, 07-17
data/processed/order_log.jsonlTIME-GATED2026-06-17, 07-16, 07-17
data/pit_snapshots/TIME-GATED2026-06-16, 07-15, 07-16 (T-1 lag)
data/equity_history.jsonlBACKFILLABLE0 — healed by backfill-equity

Causes: the 2026-06-17 Tiingo 429 and the 07-16/17 clock-rotted test_fleet_gather_and_renders fail-close. Both incidents are marked CLOSED in AUDIT.md — but both closures were written against the one backfillable stream ("gaps self-heal on today's backfill-equity", "fully green"). The three irreplaceable streams were never mentioned. data/options_skew.jsonl has all four dates, so these were ordinary trading days, not calendar effects.

Why nothing caught it. thales captures reports only last + staleness_td of the newest file — I ran it: every stream reads OK, including all three carrying holes. capture_qa() iterates over files that exist, so an interior hole is invisible by construction. Meanwhile ops/DAILY_AUDIT.md step 7 instructs "No gap days", and charter G4 asserts capture-day gaps are "measured by thales captures / capture-qa, machine-checked". That claim is false today — a pinned gauge reading pass while blind, on the asset the charter calls irreplaceable (§1 asset #2: "a missed day is a permanent notch").

Precondition, before any code: enumerate every existing interior hole across all registered streams' full history and record them in CAPTURES.md as permanent notches. G4's metric is "capture-day gaps (target zero)" and the current count is literally unknown.

Kill criterion. Negative control first: delete one interior day from a temp copy of each time-gated stream and assert the checker reds naming the date, and greens on the untouched copy. The scan must cover full history, not a trailing window, or it re-blinds by drift. Then: if the census surfaces zero interior gaps beyond the six above, AND a 20-trading-day live shadow run raises zero true alerts while raising ≥1 lag/calendar false positive, abandon the standing detector — record the six notches and stop. Do not tune the detector to survive.

Owner judgement required: G4's wording is in the hash-frozen §1–§5 span. The honest fix may be to make the gauge true (build the check) rather than amend the text — but if amendment is wanted, that is a countersigned append, not a panel action.


5. B4-NOTCH — the skew stream's calendar notch has already destroyed 1 of 9 accrued weeks of a hash-frozen test, and the option to say so expires. · judgement: YES · effort 0.25 pd (memo only) · horizon: pin a date now

Measured from owned committed data (data/options_skew.jsonl, 42 dates):

ISOwk 27 (06-29…07-03):  283 283 283 283 283   ← all five below the 400 floor
2026-07-06:              283                    ← six consecutive days
2026-08-03:              281                    ← one-day notch
all other days:          888–897

B4's activation pin (research/2026-08-01_b4_skew_activation_pin.md:71) requires ≥400 valid rows for a signal date, so ISO week 27 is skipped entirely by the pinned primary statistic — ~11% of accrued weeks gone from an already screen-grade test whose first read is 2027-06. The pin also drops an observation when the gap between signal dates exceeds 10 trading days, and the week-26 observation chained 06-26 → 07-10 = exactly 10 td, landing on the boundary; a 7-day notch would cost the adjacent observation too.

Checked and cleared: the 283-name set is byte-identical across all six days and a strict subset of the full 890-name set — exactly what "only names with weeklies qualify" predicts. It is a genuine calendar notch, not a silent cap, and therefore not a sibling of CQA-1.

Kill criterion — a forcing function, because report-only means this resurfaces in 2027-06 when amending is no longer possible without a C0 breach. The ≥400-row floor must be affirmed or amended, countersigned, by a date pinned now, and in any case before the first skew-vs-return computation. Silence resolves to AFFIRM — pin stands, notch accepted, the ~11% loss recorded as known-at-registration. Default-to-affirm is what stops the option being exercised after someone has peeked at a return.

Owner judgement required, and it is the only item here that expires.


6. CLK-1 (downgraded) — two dated criteria mature in ~10 weeks with no evaluator in the repo, and neither is on the §6 calendar. · judgement: no · effort 0.5 pd · horizon: clocks bind 2026-10-20 / 2026-10-28

Not a standalone item — this is the precondition to charter §7 rot-item 5's already-planned cycle 3 (wire the §6 deadline ledger into AUDIT.md/the digest). Recorded so cycle 3 starts from a known list.

  • options_iv_term vacuity kill — CAPTURES.md pins "at 60 trading days (~late 2026-10), cross-name median < 5 expiries/name → VACUOUS, stream RETIRED". Stream Since: 2026-08-03 → matures ~2026-10-28. No evaluator exists: capture_qa checks a daily median against a breakage bar of 2 (captures.py:481-488), explicitly commented as not the pinned criterion.
  • shortability minimum accrual — CAPTURES.md pins 60 td plus the hypothesis "excluding HTB names from the momentum top-50 changes selection ≥1 name/month", measured BEFORE any return is looked at. Since 2026-07-26 → matures ~2026-10-20. Zero consumers of the borrow flags exist outside the capture/QA path.

Adding rows to §6 changes no pinned criterion — §6 is a declared living section, so this is not a C0 boundary.

Kill criterion — bidirectional, or it is vacuous. The cycle-3 ledger check must red if any §6 row lacks a named evaluator or if any dated criterion in CAPTURES.md / a hash-frozen memo is absent from §6. Only the second direction catches missing rows; a one-way check would have passed today. If the check cannot be written without hand-maintaining a second list (i.e. it only restates §6), abandon the tooling and keep §6 as a doc with a weekly-audit review step.


7. SV-1 (downgraded) — the state schema version is un-bumpable on the two rewrite-in-place files. · judgement: no · effort 0.1 pd · horizon: n/a

execution/state.py:26–29: _stamp honours a pre-set "v". The two rewrite-in-place readers return the parsed dict including "v", daily.py mutates it and writes it back, so the version freezes at whatever was first written. Verified: with SCHEMA_V monkeypatched to 2, a read-modify-write of kelly_ledger.json still emits "v": 1; JSONL appends correctly emit 2. The code comment says to bump it "only with a dated note and a dispatching reader" — a dispatching reader would then see v=1 on a v=2 document and mis-parse. Harmless today (no v=2 exists); it defeats the mechanism precisely when first used. Not worth its own work item — fold into the next touch of state.py (stamp unconditionally at the two rewrite sites, or strip "v" in the readers), with the monkeypatch round-trip as the test.


Panel gap, recorded honestly: the change-reviewer lens did not review origin/audit/weekly-2026-08-03 (PR #87), the second open audit branch in the delta window. Flagged as a gap, not a clearance — it carries into panel run 2.

PC-1 — pin a ceiling on the "turnover cap" escape (owner judgement)

Raised panel 2026-08-06 · measured 2026-08-07 while amending PR #88.

health._check_position_count excuses any book-size excess when the last selection's turnover hit its cap. That escape currently has no ceiling, so a permanently-throttled sleeve stays "explained" at any size.

Why it is not a code decision. The bound was pinned at 2x target FIRST, as the panel required, then measured: meanrev holds 129 against a 52-name target (2.5x) — its cap binds every day, so exits defer indefinitely. So:

  • 2x reds the negative control every day → alert fatigue, the class this shop keeps paying for;
  • 3x clears 2.5x — but choosing it because it clears the observed value is the bound being fitted to the behaviour it polices, which is the exact failure PC-1 was raised to prevent;
  • unbounded (today) leaves the hole the panel found.

There is no defensible way for an agent to break that tie: every option is either an alarm you will learn to ignore or a number fitted to data. It needs a policy call — is a 2.5x book acceptable for a never-promotable control?

Options: (a) pin a ceiling on principle and accept meanrev reds until its book converges; (b) exempt the control sleeve explicitly and record why; (c) treat meanrev's book size as a known-degraded state and pin the ceiling for weights sleeves that are NOT the control.

Kill criterion: if after pinning, the ceiling neither fires in 6 months nor changes any decision, it is decoration — remove it and record that the unbounded escape was acceptable all along.

Effort 0.25 pd once the policy is chosen. Horizon no expiry, but it sits in live alerting code, so the longer it stays undocumented-by-default the more it reads as considered.

Approved

(none yet — move an item here to authorise implementation)

Built

(none yet — the implementer moves items here with date, PR and outcome)

Dismissed

  • 2026-08-06 · TECH_DEBT.md:88 stale P1 (survfree CPCV "not survivorship-free") · evidence is real — golden store reached 74% delisted coverage, RESEARCH.md:59 pins survfree-GOLDEN@504 as THE baseline, and the accurate residual already sits lower in the same file as a P2 — but NORTHSTAR §7 rot-item 3 already tracks this triage pass. Already tracked ≠ new finding.
  • 2026-08-06 · execution/daily.py stale module header (documents "Tuesdays only", hysteresis exit_band, "25% weekly") + stale comments at :1403/:1437 · P5 doc rot, bottom of the priority order, and TECH_DEBT.md:718 already binds daily.py on touch. Costs a queue row to say a docstring is stale.
  • 2026-08-06 · C6 decimal-money prerequisite as a §6 clock row · already carried by research/2026-08-01_c6_governance_DRAFT.md:242; would duplicate the owner's own draft.
  • 2026-08-06 · thales placebo-ensemble prints "over the same window" while its null runs on 33 panel returns vs 37 equity days · rider split off GAP-1 rather than bundled (bundling two findings under one row is how padding enters). Align the windows or restate the string before it goes decision-grade ~2026-09-08. No queue row.
  • 2026-08-06 · weekly-option availability per name, newly derivable from the B1 expiry ladder · triple-killed: B1's consumer class is pinned against stock selection, it is a near-degenerate size/liquidity proxy on Russell-1000 (the same degeneracy that killed the binary borrow flag), and it sits inside the thrice-ratified exhausted large-cap characteristic space.
  • 2026-08-06 · re-proposing panel items A5 (TRACE bond-tape probe) and B3 (SEC fails-to-deliver acquire-and-freeze) · never built and still outstanding, but already delivered to the owner in the versioned 2026-08-01 report. Re-proposing would duplicate, not add. Noted only so the empty queue is not read as "nothing outstanding".