Run one bounded EMC research cycle, select a useful deliverable, claim ownership, and preserve the outcome without overlapping writers.
Resources
1Install
npx skillscat add trimcrae/rare-cancers/research-loop Install via the SkillsCat registry.
Research cycle
Read research/autonomy/OPERATING_PROTOCOL.md first. It supersedes historical work selection,
unbounded hardening, synchronization-after-every-edit, and generic mandatory maintenance detours.
Identify the execution backend and real repository revision. Read budget posture. A legacy
scheduled fire usescadence.py --check; a direct user task is not a scheduled fire. Diagnose
relevant health failures, not every unrelated row. Never bypass spend or ownership constraints.Prioritize PUB-ASO's concrete release tasks. Read existing evidence before commissioning work.
Choose one finite deliverable with a stop condition. Do not launch another paper while the
priority package has self-doable work. Independent read-only evidence tasks may run in parallel.Claim before writing. A remote legacy cycle uses
claim.pyon current main and must successfully
publish the claim. Local Codex runs use their runner lock only after the legacy scheduler has
been paused/drained. These locks are not interchangeable. No remote claim means no remote owner.Put writing workers in isolated worktrees; the coordinator owns shared state and integration.
Keep logs in task-specific durable paths. Record model, duration, output, and validation scope.Execute the task. For reviews use
paper-hardeningand its finite iteration rule. For changes
use relevant checks thenrepo-gates. Process maintenance is a separate owned task.Integrate at a settled checkpoint after checking current main. Preserve branches until work is
integrated. Do not push each observation separately or silently overwrite concurrent state.For an outward act, evaluate the existing authority and all applicable publication clauses on
the actual package. PUB-ASO does not auto-post to aiXiv. Prepare any human submission handoff.Record failures and unresolved work precisely. A task with no advance is a valid result; another
review, a launch, and a queue update are not themselves scientific progress.The next cycle starts from durable artifacts, not a copied narrative. Do not claim a replacement
scheduler is running until its actual execution and output have been observed.Write the receipt — allocate its id, never derive one by eye:
python3 -c "import sys;sys.path.insert(0,'research/autonomy');import ids,session_cap;print(ids.next_receipt('research/autonomy/receipts',session_cap.session_id() or ''))",
and put that samesession_cap.session_id()in the receipt'ssession_idfield.
⛔⛔ READ IT FROM THE ENVIRONMENT; NEVER TYPE IT, AND NEVER INVENT A LABEL. Measured
2026-08-28: scheduled cycles typed the literal"scheduled-routine-session"into that field, so
nine consecutive cycles were INDISTINGUISHABLE FROM ONE SESSION'S to every reader — and the two
readers that matter arehealth.py:c_cycles_are_sized, which grades the session-shape rule, andsession_cap.py, which decides whether this session has earned the right to stop. A session
whose receipts do not name it cannot show it is at its cap, so the hook keeps demanding another
cycle and it runs nine.CLAUDE_CODE_SESSION_IDis set in this harness — checked, not assumed.
⛔ Measured 2026-08-27 (AUT-PROP-013): every session computedmax(committed) + 1from the same
committed state, so concurrency was outside the derivation BY CONSTRUCTION. Two sessions 50 s
apart both tookCYC-0016and the second would have SILENTLY OVERWRITTEN the first; the same
hour both filedAUT-PROP-009andAUT-PROP-010for four different items. AndAUT-PD-012was
issued twice by SEQUENTIAL cycles, which kills the comfortable reading that this is a race — the
derivation collides on its own. The id now carries a discriminator from your session id, so two
cycles can share an ordinal (they are both genuinely the Nth cycle) and still never share a file.
⭐ New ledger entries the same way —ids.next_entry_id("AUT-PD", entries)— andpriority.pynow REFUSES a ledger with a duplicated id rather than ranking it.
Contents ofresearch/autonomy/receipts/<cycle-id>.json: what you took, what
changed, what it cost, your session id, what is now queued,blocked_by[](each with the
path of whatever refused you — §6 depends on this),ended_utc(below), androute_advanced: the id of the live route you moved, or the literalnone.⛔ AND
subagents.max_concurrent— SPELLED EXACTLY THAT, INCLUDING0FOR A CYCLE THAT
SPAWNED NOBODY. It is the only keyhealth.py'sfanout_is_governedreads, and it is the
dial the architecture records as having failed catastrophically. ⚠ Measured 2026-08-27 over
all 22 receipts (AUT-PD-013): seventeen of them used three different schemas —max_concurrent, thenconcurrent_max, thenlaunched— so the row printed a FALSE ABSENCE
for cycles whose fan-out was recorded plainly under another name.launchedis not a
substitute: it is the serial total over the cycle, and the cap governs concurrency — five
launched one at a time and five launched together are the same number and different acts.
Record the serial total too if you like; record the width regardless.
⭐ This is now a preflight gate, not a convention —python3 research/autonomy/receipt_schema.py --checkowns the name for the writer and the reader both,
and refuses the commit that lands a receipt without it. A field name agreed in prose between
two files is a hope; this loop had already got this one right twice and lost it twice.⛔ AND
ccr_session_id— THE OTHER SESSION ID, WHICH IS NOT AN ALTERNATIVE TOsession_idBUT A SECOND FIELD ALONGSIDE IT. Fromreceipt_schema.FIRST_CCR_GOVERNED_CYCLEonward, a receipt without it FAILS PREFLIGHT and
therefore the commit. Two id spaces, two sets of readers, and neither substitutes for the
other:session_idstays the harnessCLAUDE_CODE_SESSION_ID— a bare UUID — becausehealth.py'scycles_are_sizedandsession_cap.pyboth key on it;ccr_session_idis thesession_01…id the SESSION LIST speaks, and it is the only thingsession_reaper.pyandholder_liveness.pycan use to join this receipt to a session row.
⭐ READ IT, NEVER TYPE IT, exactly as forsession_id— the claude-code-remoteget_sessiontool called with no arguments returns your own row and the value is.ccr.id(re-verified 2026-08-29).
⚠ Measured 2026-08-28 (AUT-PD-124): most committed receipts carried no joinable id at all,
so the reaper could not show that a single modern session had delivered — it archived nothing
and reported delivered cycles as ones that may have died holding uncommitted work.
⭐ Spell the receipt's own id incycle_idtoo.receipt_schema.auditfalls back to the
FILENAME when that key is absent, so a receipt whose two disagree is graded under whichever
one the reader reached for.⛔ AND
ended_utc— THE RECEIPT'S OWN CLOCK, WRITTEN WHEN THE CYCLE ENDS, AS AN ISO-8601
UTC INSTANT (2026-09-02T15:13:00Z). Required fromreceipt_schema.FIRST_CLOCK_GOVERNED_CYCLEonward; earlier receipts are grandfathered.
⛔started_utcIS NOT A SUBSTITUTE AND NEITHER IS ANY OTHER SPELLING.health.py:c_cycle_deliveringasks whether a fired cycle delivered, and the start is
already owned bycadence.py --stampin step 1 — writing it here too gives one fact two
homes, and dates the firing rather than the delivery.
⚠ Measured 2026-09-02: every receipt from CYC-0084 on carriedstarted_utcand none of the
end-time spellings health.py reads, so all fifteen sorted to the FRONT of a(timestamp or "", filename)sort andreceipts[-1]resolved to the newest receipt still using the OLD
name. The board reportedLATE — the last receipt is 103.5 h oldfor seven consecutive runs
while twelve receipts had landed inside that window, the newest 2.7 h old — andadvancing_live_workread the same stale tail and reported NOT-ADVANCING against three
four-day-old receipts. One field name, two false reds. ⭐ *It was omission, not a redesign:
this contract had never named a receipt clock in any of its 25 versions, and the two
spellings interleaved within the same hours rather than switching over. A field name
agreed in prose between two files is a hope — this is the fifth time that has cost
something here, so the clock is now a*_KEYconstant the gate refuses a receipt without.⭐⭐ AND THIS PARAGRAPH IS NOW MEASURED AGAINST THE GATE, WHICH IS WHY AUT-PD-146 COULD NOT BE
CLOSED BY WRITING IT (2026-08-29).python3 research/autonomy/contract_check.py --check
DERIVES — by deleting fields from receipts the enforcer accepts and re-running it — the set of
fieldsreceipt_schema.pyrefuses a receipt for, and fails the build when THIS STEP does not
name every one of them. ⚠ccr_session_idhas been a commit-failing requirement since
CYC-0070 while this text never mentioned it. All seven receipts written since do carry it —
so the gap cost no build — but CYC-0073-d4ccfde4 recorded that it wrote the field only
because it had openedreceipt_schema.pyfor an unrelated reason. That is compliance by
luck, and luck is not a mechanism. A field name agreed in prose between two files
is a hope — this repository has now lost that agreement four separate times (AUT-PD-013's
fan-out key, AUT-PROP-013's ids, AUT-PD-037's serialization, AUT-PD-146's) — so the
agreement is checked rather than restated, and a new requirement in the enforcer reds the
build until this step names it.
This receipt step applies to the existing remote Claude cycle. A local Codex receipt is a
separate runner outcome, not a fabricatedCYC-*record; do not inventccr_session_id.
Publish/notify only through authorized existing paths. End with the result and next action.
The legacy reference preserves backend-specific trigger, claim,
and receipt mechanics. Consult only the relevant sections. It is not an instruction to resume
unbounded loops or apply Claude-only session APIs to Codex.