- _execute_qualify: deterministic-first — 13 implementation-readiness checks
(design explicit, interfaces concrete, authority boundaries, data flow,
sequence, invariants, tests, acceptance, failure modes, rollback,
adversarial mitigations included, falsification tests included, blocking
unknowns absent) run BEFORE any MiMo call
- MiMo semantic judgment ONLY when deterministic checks pass: judge whether
a coder could implement without guessing — not re-research, not re-litigating
the design (ADVERSARIAL owns epistemics)
- Verdict assembly: deterministic failures → REFINE_SYNTHESIS without a model
call; worker verdicts constrained to PROCEED/REFINE_SYNTHESIS; informal
reopen attempts downgraded to REFINE_SYNTHESIS; formal reopen requires the
four justification fields (gate-enforced)
- Malformed worker verdicts treated as REFINE_SYNTHESIS (fail-safe)
- 9 new tests; suite 174 pass, ruff clean
Real-run finding from #161: the worker integrated all 10 adversarial
mitigations into its design language (60-100% key-concept overlap in
structural fields) but did not quote the mitigation sentences verbatim.
Literal substring matching tested copy-paste, not design coverage. The gate
now measures keyword-overlap coverage (≥50% of non-stopword key concepts)
across invariants/interfaces/data_flow/tests/acceptance.
Verified: 10/10 mitigations represented, 7 kill-tests converted to 13
invariants, 11-step implementation sequence → GATE PASSED (e584bbf895b3),
tree at QUALIFY.
- _execute_synthesis: deterministic readiness invariant enforced BEFORE the
MiMo call (no cognition on untrustworthy foundations); bounded prompt
carries survivors, rejected candidates + reasons, required mitigations,
and kill-tests as ACCEPTED CONSTRAINTS
- Worker produces build instructions: selected_design, why-this-design-won,
rejected_alternatives, components_affected, authority_boundaries,
interfaces, data_flow, implementation_sequence, invariants, tests_required,
acceptance_criteria, failure_modes, rollback, deliberately_excluded
- Gate (_validate_synthesis): all core fields present (design, sequence,
interfaces, acceptance, rollback); every falsification test converted into
an invariant; every required mitigation represented STRUCTURALLY
(invariants/interfaces/data_flow/tests/acceptance — prose mention in the
design summary is not representation); rejected-candidate resurrection
forbidden (id scan across design + components)
- Artifact carries provenance: adversarial_disposition, carried_mitigations,
carried_falsification_tests, carried_survivors — the literal-handoff input
shows exactly which constraints the design was built under
- 8 new tests; suite 165 pass, ruff clean
Real-run findings from #161: worker emits snake_case dimensions
(weak_assumptions, Rune_constraint_violations) — gate now normalizes
snake/kebab to spaces before matching. Falsification may be satisfied either
as an attack dimension or as dedicated falsification_tests (kill-tests) —
the worker produced 2 concrete kill-tests (e.g. FT1: run harness with
embedding backend unreachable; any unlabeled mode switch voids validation).
Verified: 16 attacks, 6 survivors, 1 rejection (memory-adapter-protocol —
duplicate authority vs memory_selector.py source registry), 2 external
checks, 25 repo-verification tool calls → GATE PASSED (3827446e701a),
tree at SYNTHESIS.
- _execute_adversarial: one bounded MiMo red-team pass on the leading
APPLICABILITY candidates; targeted external checks permitted with receipts
- Dossier: surviving/rejected candidates, attacks (dimension+finding+evidence+
severity), critical assumptions, required mitigations, research gaps,
falsification tests
- Gate (_validate_adversarial): rubber-stamp fails (zero attacks); all seven
attack dimensions must be addressed; every leading candidate must be
attacked at least once; a fatal attack on a surviving candidate requires
an explicit rebuttal
- File channel accepted whenever stdout parse yields nothing (established
worker pattern); stage-ordering guard (no APPLICABILITY → STAGE_FAILED)
- 7 new tests; suite 130 pass, ruff clean
Prose-only stdout with a valid applicability.json on disk previously produced
an empty artifact stub (gate-fail with misleading 'no candidates' error).
Now: stdout JSON parsed first; if no candidates, the dossier file channel is
checked. Structured artifact remains the authority. Verified on real #161 run:
9 candidates + repo-verified receipts → GATE PASSED (f477543dc8c9),
route STANDARD_SOLUTION_SUFFICIENT → ADVERSARIAL (INNOVATION skipped on merit).
- _execute_applicability: one bounded MiMo pass consuming ISSUE+INWARD+OUTWARD
artifacts; bounded inputs (source/claim ids, not prose); structured dossier
written by worker to applicability.json — file channel accepted (structured
artifact is the authority), stdout JSON also parsed
- Gate (_validate_applicability): route must be STANDARD_SOLUTION_SUFFICIENT
or INNOVATION_WARRANTED; candidates must use canonical classifications;
viable candidates (DIRECTLY_APPLICABLE/APPLICABLE_WITH_ADAPTATION/
PROMISING_FOR_INNOVATION) require integration_point AND evidence ids;
source_ids must resolve to accepted OUTWARD sources (no fabricated citations)
- Route decision drives conditional INNOVATION routing at advance()
- Stage-ordering guard: no OUTWARD artifact → STAGE_FAILED without MiMo call
- 6 new tests; suite 123 pass, ruff clean
The structured artifact IS the authority (§5); stdout text is just one
delivery channel. When the relay collects no stdout texts but the worker
wrote a valid dossier to the stage artifact file, the executor normalizes
it into the canonical unified structure (_normalize_outward_artifact) and
gates it the same way. Gate semantics unchanged: ≥2 sources, real read
receipts required, tool receipts required.
Verified against the real #161 OUTWARD run: worker dossier (16 sources,
4 read with 22 claims, 4 contradictions, 8 limitations) + relay receipts
extracted from the live events file (4 queries, 4 reads, 22 tools) →
GATE PASSED, tree advanced to APPLICABILITY. Artifact hash bc57c4600880.
- ResearchEventRelay captures per-event tool receipts (QUERY / SOURCE_READ /
TOOL / TOOL_FAILED with url + timestamp) — the QUERY→SOURCE→READ→CLAIM
chain now has real provenance, not just counts
- OUTWARD artifact embeds tool_receipts; sources carry read_receipt flag —
a source counts as read only if the relay captured a real fetch
- _validate_outward fixed to read the unified (discovery/reading) artifact
structure — previous validator read top-level keys that never existed,
so any real run would have gate-failed; now also enforces ≥1 actually-read
source and non-empty tool receipts (search snippets alone ≠ evidence)
125 tests pass, ruff clean.
THE HEAVY RUNNER IS THE DESKTOP PC, NOT THE PI.
- scripts/runner_evidence.py: runtime execution evidence capture (hostname,
runner identity from env, labels, execution class, stage, process id,
issue, research process id, timestamp) — identity captured at runtime,
never inferred from configuration alone
- Deterministic placement gate (validate_placement): desktop-heavy stage
executed on Pi → PLACEMENT_REJECTED; hostname checked too (cannot hide
the Pi behind a desktop runner name); missing evidence fields rejected;
unverified runners rejected (no silent fallback); pi-lightweight forge
operations (metadata, labels, eligibility) explicitly allowed
- Placement gate wired into StageExecutor.execute(): every stage result
embeds runner_evidence; Pi execution fails the tree and cannot count
as acceptance
- docs/RESEARCH_CODE_CLASSIFICATION.md: aggressive detection, conservative
classification (CURRENT/SUPERSEDED/COMPATIBILITY/TEST_FIXTURE/
UNKNOWN_PURPOSE); NO deletion in #265 — retirement is RP11 after RP10
proof; run_full_research() self-walk + --watch poll classified SUPERSEDED;
tui-restart-pending marked SUPERSEDED CANDIDATE (verify before removal)
Acceptance: canonical order PASS; conditional INNOVATION PASS; ISSUE
deterministic PASS; earliest-stage refinement PASS; heavy-runner requirement
encoded PASS; Pi rejection PASS; runtime identity/hostname/labels captured
PASS; stage/run/process identity PASS; Gitea stage authority preserved
(one-stage executor unchanged) PASS; no self-walking production loop in the
canonical path PASS; no premature retirement PASS.
125 tests pass, ruff clean. Pre-existing henxels digest env failure unchanged.
Canonical handoff 2026-09-08 (epic #257, children #264-#277):
- ISSUE (deterministic issue-quality gate) replaces DEFINE as stage 0 —
the Gitea issue is the authoritative definition; no cognitive DEFINE stage
- APPLICABILITY added after OUTWARD with canonical candidate classifications
- INNOVATION precedes ADVERSARIAL (canonical order per §1)
- OUTWARD unified (OR1 preserved): discovery+reading are artifact subsections
- RETIRED_STAGES declared: DEFINE/OUTWARD_DISCOVERY/OUTWARD_READING cannot
be advanced or dispatched through the canonical path (RETIRED_STAGE status)
- more_research → targeted refinement routing to EARLIEST deficient stage;
external unknowns route to unified OUTWARD, never the retired split stages
- fake worker, integration/boundary/reliability tests rewritten for canonical
chain; legacy-state tree at a retired stage raises on advance
- new prompt templates: issue.md, applicability.md (with MUST NOT contracts)
Tests: 104 passed. Ruff clean. Pre-existing henxels digest failure unchanged
(missing henxels.contract module in env, fails identically on clean tree).
closes#264 (RP1 stage model); runner-placement enforcement (RP2/#265) next
One coherent MiMo external-research pass. Discovery and reading remain
visible as subsections inside the artifact, not separate top-level stages.
Artifact has discovery and reading subsections. Event relay streams both.
- _execute_outward_discovery: compiles stage-scoped prompt from tree state,
invokes MiMo locally with live event relay, extracts URLs from research text,
builds OutwardDiscoveryArtifact (queries + sources + coverage + selection)
- _validate_outward_discovery: fast gate (≥2 sources, unique IDs, valid URLs, coverage)
- Boundary test updated: OUTWARD_DISCOVERY reaches the MiMo invocation boundary
(subprocess mocked in unit tests, real MiMo in acceptance)
BUILD ONE COGNITIVE STAGE. PROVE ONE COGNITIVE STAGE. THEN REPEAT.
Per RUNE_RESEARCH_STAGE_TRIGGER_BOUNDARY_ACCEPTANCE_PROMPT:
- Each stage is a separate ResearchStageReady event
- The executor runs exactly one stage per invocation
- Duplicate events are acknowledged as DUPLICATE (no second execution)
- Gitea independently authorizes the next stage
- Rune does NOT self-advance through the methodology
- DEFINE is deterministic, INWARD is deterministic context builder
- OUTWARD_DISCOVERY returns NOT_IMPLEMENTED (proves the boundary)
7 boundary tests green.
DEFINE: deterministic contract compilation from Gitea issue. No MiMo unless ambiguous.
Classifies question type, extracts sub-questions, produces required-stage plan with skip reasons.
INWARD context builder: deterministic. Matches issue keywords against umbrella manifest
and source/test files. Produces bounded candidate context. MiMo only invoked for
semantic inspection of the bounded candidates, not broad agent wandering.
METHODOLOGY STAGE ≠ MANDATORY LLM CALL.
Gitea triggers and ratifies cognition. Rune/MiMo performs cognition. The TUI shows the live process.
The backend runs LOCALLY on nick's machine (not in Gitea Actions):
- receives stage-ready events (polls Gitea for research-pending issues)
- compiles stage prompts from the ResearchTaskTree
- invokes mimo LOCALLY (subprocess — not Gitea Actions)
- streams events to the TUI via the event relay
- validates artifacts with stage-specific gates
- records results in Gitea
- advances to the next stage
Gitea Actions becomes a TRIGGER, not a WORKER.
RSC1: ResearchTaskTree tracks issue+round+stage+completed+artifacts+blocking_unknowns.
Persists to task_tree.json per issue. advance/fail/more_research/capsule/get_prior_artifacts.
RSC2: compile_research_prompt(tree, stage) builds a task-specific prompt from
the stage template + process capsule + prior artifacts + blocking unknowns.
RSC3: run_research_stage.py executes ONE MiMo invocation for ONE stage.
Compiles prompt → invokes mimo → extracts text → advances tree → returns next stage.
The workflow dispatches per-stage (Gitea event model IS the scheduler).
R1: disposition routing wired into the research stage workflow — PROCEED
lets research-clear fire, MORE_RESEARCH dispatches round+1 immediately
(no sweep wait). The workflow completion IS the event.
R2: 8 stage-scoped prompt templates (DEFINE through QUALIFY) — each gets
a narrow prompt, frozen prior artifacts, explicit MUST NOT constraints.
R3: research_router.py — classifies questions (academic/software/web),
routes to OpenAlex/SemanticScholar/arXiv/GitHub/DuckDuckGo, doctrine
queries include limitations/alternatives/failures.
R4: structured artifact validated in the stage workflow before commit —
receipt + URLs + code paths + disposition all checked.
R5: MORE_RESEARCH dispatches round+1 from the workflow (no sweep wait);
blocking unknowns passed to the next round.
R0 red reproduction recorded: foreground slept 300s×N while child mimo
worker was productive (18.6% CPU, 547MB, 8min CPU). Anti-pattern confirmed.
- research-dispatcher.yml: triggers on issues.labeled, checks label via API
(deterministic), dispatches research-stage via workflow_dispatch (proven).
Fixes the label-event payload quirk that silently skipped API-applied labels.
- research-stage-headless.yml: dispatch-only trigger (dispatcher handles label
events); structured artifact generation from mimo events (receipt + URLs +
code paths + disposition) validated by research_artifact.py before commit.
- The prose comment stays as human-readable rendering; the ARTIFACT is the
gate authority (Nick: replace prose-regex authority with structured
artifact validation).
## Bumps are bookkeeping, not reviewable code
auto-version now commits the 5 version surfaces DIRECTLY to master via the
contents API — no bump branch, no bump PR, no bump CI wave, no staleness
cascade, no concurrent-bump race (230/231 proved it). In-memory gate: all 5
surfaces must agree on the new version BEFORE any commit (TUI footer renders
rune.__version__ at call time — the displayed version IS __init__.py).
Commit messages carry the provenance link to the earning PR.
## Robot: transient 405 'Please try again later' retried (3x backoff)
Forge mergeability recompute was failing the robot on every merge burst —
transient is 'not ready', not a refusal. Fail-loud applies to refusals and
to exhausted retries (both paths tested).
## Future-proofing: docs/pipeline-extension-guide.md
Placement decision tree (merge-deciding → required-checks; work → narrowest
event; bookkeeping → direct commit), event-delivery quirk table (every entry
learned by a live failure), per-type checklists, the three invariants.
Registry links it. Any future agent reads the guide BEFORE adding a process.
Version bumps are version-string changes; the full pipeline is the wrong
tool. Per Nick:
- provenance: bump PR body now RECORDS the PR that earned the promotion
(Earned by: #N — title, with link) — the promotion has a paper trail
- light gate: bump PRs skip the 7 heavy jobs (head-ref if); required-checks
runs scripts/check_version_surfaces.py instead — proves all five version
surfaces agree (__init__ canonical, rune_tui header, canary literal,
pyproject wheel, README badge) so the TUI footer will render the new
version (VersionLabel reads rune.__version__ at call time)
- red-first: 7 tests for the surface gate; full suite 877+84
Behind-base self-heal previously forced a full CI re-run even when the
update's tree diff touched no code path — redundant by construction
(identical code-path trees cannot produce different test outcomes).
- merge_robot emits UPDATED protocol line after self-heal
- scripts/carry_forward_check.py classifies the tested-head→new-head tree
diff: CARRY_FORWARD (docs-only) vs RERUN_REQUIRED (any code-path touch)
- workflow consumes it: CARRY_FORWARD republishes the required-checks
aggregate citing tree identity; RERUN_REQUIRED waits for CI as today
- policy recorded: first pass = full pipeline; base movement with
unchanged code paths = carry; bugs/mistakes = new commits = full re-run
Red-first: 2 new tests (classification + companion script contract).
THE INEFFICIENCY (Nick): every master movement staled every other open PR;
the robot SKIPPED them; a human worktree dance updated branches by hand; CI
re-ran; the next auto-bump staled everything again. Loop documented across
the whole session (round-1/round-2 'behind-base update' commits).
Fix: the robot performs POST /pulls/{n}/update itself on 405-behind, then
retries the merge. Skips only genuine conflicts (409). The forge heals its
own staleness — no agent, no human.
- test_behind_base_pr_is_updated_then_rechecked (new contract)
- test_update_conflict_409_skips_not_fails
- test_behind_base_405_self_heals_then_merges (supersedes old skip contract)
- suite: 13 robot tests green
scripts/run_agent_stage.py per the Forge Cognitive Research Loop handoff:
- ONE runner for all cognitive stages (research/planning/review) — not a
family of per-stage scripts
- tool receipts derived from STRUCTURED events (type:tool parts), never
model self-report; OUTWARD validity = >=2 completed search/fetch calls
across >=2 distinct web domains
- idempotency key issue+stage+round; TTL locks (crashed stage -> stale lock
expires -> redispatch safe)
- fail loud: mimo crash, schema violation, missing receipt all return 1
- event schema verified live against runner mimo 0.1.13 (type:text/part.text
— the spec-gate's parser had the wrong shape; fixed here)
- canary evidence (T8): no native search tool on runner mimo; discovery
works via webfetch of search-engine HTML + fetched pages (2 domains,
real content, honest self-report of the fallback)
Root cause of missing version bumps since 2026-09-06 15:01: #190 pasted
inline python into a double-quoted YAML scalar (unescaped quotes, column-0
lines) making auto-version.yml UNPARSEABLE. Gitea rejects the workflow
wholesale — dispatch 500, pull_request:closed triggers never fire. Six
merges tonight + all merges since #190: zero bumps.
- scripts/semver_bump.py: testable next_version() (0.x semver conventions)
- workflow repaired, parses clean
- version-behind-tag now a LOUD ::warning anomaly (#192), never silent skip
- NEW GATE: tests/test_semver_bump.py validates ALL workflow files parse —
the missing lint that let this rot silently (7 workflows covered)
Nick's critique structuralized (2026-09-07):
- 'is research being done or already done?' → sweep detects existing
4-phase rounds (INWARD/OUTWARD/INNOVATION/SYNTHESIS comments) and SKIPS
those issues; never re-researches completed work
- research must CALL the LLM → research-stage-headless.yml dispatches headless
mimo on research-pending label events (spec-gate runner pattern, verified)
- filed as a research FILE per issue → research/issue-<n>-research.md merged
to master via auto-PR; comment carries the markers research-clear keys on
- 4 phases → SYNTHESIS ends in ordered build instructions = implementer's prompt
- TUI callback seam → prompt externalized to governed prompts/research-round.md
(placeholders substituted at runtime; L3/TUI owns it via reviewed diffs later)
- failsafe: sweep dry-run by default; --apply explicit; red-first tests (10)
Also: spec-gate-headless.yml copied to master stream with ROBOT_TOKEN wired
(same reserved-secret bug the robot had — GITEA_TOKEN is the read-only auto token).