ARN-65: harden Foresight no-wedge reliability - #422
Conversation
The crystallized 0.3 formulation: decompose imagined futures into separable claims, search routes per claim with bounded revision and pruning, and price amendment (deformation flags) so drifting back to consensus is never free. Governing commitments recorded: cost constants are tunable priors, authored-claim resolution stays judged, evidence adapters are pluggable, amendment is never silent. RFC opens with the one-paragraph formulation; ADR-002 points here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One separable load-bearing assertion of an imagined future (ADR-004). States Proposed→Bridging→Settled|Unreachable|Failed; amendment only via the diff-carrying AmendText action; original_text frozen at creation; route/round bookkeeping for the search budget; settled_route_id as the per-claim single-writer guard. Registered in the corridor spec contract (red first) and the merged-CSDL uniqueness gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Endpoint.SubmitForRepair now spawns a decomposer session that splits the bundle into 3-8 separable load-bearing claims (DecompositionComplete); the module creates Claim entities (original_text frozen) and fans out Claim.SubmitForBridge in chunks of 2 through the bounded SpawnNextBridge self-loop. spawn_repairers refits to per-claim routes: the Path gains claim_id/route_index/round_count/revision_brief, the bundle is inlined into the repairer prompt (the v1 temper.read silently failed — walls 13/15), and the prompt carries the AmendText + deformation drift contract and the UpdateEdges depends_on contract. Cedar: Claim section (search loop system-only, AmendText relay-writable), decomposition and UpdateEdges relay permits; matrix pins the verdict denials. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…scade aggregate_costs v2 owns the deterministic search policy (ADR-004), all constants named tunable priors: the prune gate cuts routes whose repairer-phase cost already exceeds the claim's best ceiling before any adversary spend; expensive objections (any flag >= 20 pts) within the 2-round budget send the route back to its repairer with a brief of the objections; claims settle on the cheapest acceptable route (<= 60), open alternate routes (max 3) briefed on standing objections, or are honestly MarkUnreachable; the claim -> endpoint -> world cascade derives endpoint weights from per-claim verdicts (unreachable claims price at the bound) and reports PathsScored once, guarded by canonical_path_id. Deformation flags price like contradictions so amendment toward consensus is never free. Legacy claim-less paths keep the 0.2 classify pass verbatim (in-place evolution). spawn_repairers gains revision mode; the Path spec gains RequestChallenge/Prune/RevisionRequested and the Pruned state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On a 'no' resolution, evidence_ingest walks the depends_on edges (declared by repairers via EventNode.UpdateEdges) transitively — cycle-safe, depth-bounded — retiring every node whose premise died, then re-prices settled claims whose routes required dead or invalidated nodes (+50/node, a contradiction/high: the bridge now contradicts reality). A claim flipping classification dispatches World.BeginUpdate, alongside the existing canonical-route check. Walk and reprice are pure and pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… CSDL World gains AnimateDwellers/DwellersCast/SpawnNextDweller (casting + bounded dweller fan-out) and AdjudicateNodes (judged resolution, as-of date as data); EventNode gains AdjudicationVerdict (session-reported, module-validated — sessions never decide truth) and FlagForHuman; Dweller.RecordContradiction now carries claim_id + note and triggers the flag-feedback reprice. Cedar: operator/system/relay permits per the matrix; crate skeletons for animate_dwellers and adjudicate_nodes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stories One casting session proposes 2-3 personas grounded in the settled claims; the module creates Agent + persona File + Dweller entities and fans out one 50-turn traversal/story session per tick (bounded self-loop). Dwellers live the canonical route as biography, record traversals, file contradictions — which append to the route's challenge flags and re-price the claim (contradiction/low, 12.5 pts, a tunable prior) — and write first-person stories submitted through the consistency gate. Gate-cleared artifacts now publish automatically (PassCheck triggers the gate's publish arm): stories reach the feed without an operator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
adjudicate_nodes: on World.AdjudicateNodes, overdue authored Confirmed nodes (cap 5/dispatch, loud) each get one adjudicator session — web evidence, rubric, self-reports EventNode.AdjudicationVerdict; the module alone validates (yes/no AND confidence >= 0.8 AND >= 2 unique evidence refs) and dispatches Resolve or FlagForHuman. Hindcast worlds never adjudicate; sessions are never told to decide truth directly. 18 tests. Hindcast library: three new frozen fixtures (2024h2 AI coding tools, 2025h1 LLM API market, 2023h2 consumer assistants), corpora vantage- clean, actuals fact-checked; README documents that entry order and needle narrowness are load-bearing for the matcher. calibrate_constants.py: offline grid over kind weights / severities / decay / tail ceiling, recomputing deterministically from recorded flags (no model re-runs), ordering constraint enforced, honest about what the metrics cannot identify; --self-test pins the recompute against aggregate_costs' exact vectors. Cedar: session-facing AdjudicationVerdict permit (mirrors CheckerVerdict); artifact PassCheck trigger renamed consistency_gate_publish (trigger-name ambiguity rejected the bundle — caught by verify_specs, boot now clean). Independent review: PASS after two re-review rounds; both P1s (Cedar gap, fixture compound-question ordering) verified fixed empirically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The host's Cedar gate allowlists modules by name for http_call and access_secret; decompose_endpoint, animate_dwellers, and adjudicate_nodes were missing, so the decomposer's first outbound call died with 'no matching permit policy' and both endpoints failed (found live, run 1). Live tenant policy patched via the policies API in the same shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ClaimsAttached and SpawnNextBridge are module-dispatched (system); DecompositionComplete and DwellersCast are session self-reports (mirror SubmitForRepair/SeedComplete). Found live: the fan-out died 403 after the decomposers had created all 15 claims. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ution Run 1 produced 15 scored routes costing 90-312.5 (median ~245) against an acceptability bound of 60 — every claim was marked Unreachable and the verdict vector degenerated. The bound was set before any live claim-level flag data existed. New priors: acceptability 240, reachable 120, revision threshold 40 (at 20, nearly every route revised and session spend tripled), unreachable penalty 240. Mirrors updated in evidence_ingest and animate_dwellers. Still priors (ADR-004 commitment 1); the hindcast calibrator owns the next revision with outcome data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first live run never finished unaided: the debug server aborted under v2 concurrency and every interruption orphaned in-flight sessions, leaving Paths stuck in Solving/Repaired/Challenged and Claims in Bridging with no re-drive — recovered only by dozens of hand-dispatched actions. Fix: release build for runs (debug can't take the load), and replace the corridor wait states' allow_indefinite_states with state_timeouts whose on_timeout re-spawns the dead work — ResumeRepair (re-spawn repairer), RequestChallenge (re-spawn adversary), ResumeCosting (re-run costing), ResumeBridge (re-run the claim decision). Budgets sit above a healthy session so live routes never double-spawn; crash recovery rides the ADR-0056 hydration re-arm. All re-drives are system-only in Cedar and reuse existing spawn modules (no duplicate entities). The search budget bounds the self-heal. verify_specs 10/10, contract+matrix green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ctions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (D1) Pure cosine/distance + the four decision functions the consumers need: is_diverse + farthest_point_order (D3 diversity gate), cluster_by_threshold (D4 synthesis: cross-world agreement), nearest (D2 reconcile, D4 hindcast match), and parse_embeddings (Ollama batch/single + OpenAI shapes, so the endpoint is swappable by secret). The semantic judgment comes from the external model (mxbai-embed-large via Ollama); these decisions are pure, deterministic, in-WASM. 7 golden-vector tests; compiles to wasm32. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ggler (D0) claim_decision waited on ANY in-flight route, so a claim with a cheap-enough scored route could deadlock forever behind a wedged straggler (a Path whose adversary died and whose Repaired timeout keeps re-spawning). That made ADR-007's "search budget bounds the self-heal" promise false in practice. Gate the wait: a still-churning route only blocks the claim while the outcome could still change — when the route budget allows a fresh alternate, or when no acceptable route exists yet. Once the budget is spent (route_count >= MAX_ROUTES) AND an acceptable route exists, settle on the cheapest acceptable route; a stuck or flapping route can only match or slightly beat one we'd already accept. A claim with no acceptable route still waits for its last in-flight route. Refine ADR-007's consequences to describe this claim-level bound accurately (the route-level round/route budget alone does not bound a flapping adversary). Red-green TDD: added wedged-straggler / budget-left / no-acceptable cases to claim_decisions_wait_search_settle_and_give_up_honestly. 14/14 host tests pass (cargo test --target aarch64-apple-darwin); aggregate_costs.wasm rebuilt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing) The repairer proposed authored EventNodes with a bare "resolve_by: YYYY-MM-DD" instruction and no date guidance, so it eyeballed tidy month/quarter ends (run-1: six nodes dated to round dates in one backward pass). The world's lag_table_file_id and target_date were never read. Read both from the world in both spawn modes (SubmitForBridge and RevisionRequested/ResumeRepair), fetch + inline the lag table file, and add a DATE DISCIPLINE block to the repairer prompt: date each node by adding the historical lag for its transition (from the inlined table) to its prerequisite's date, between TODAY and the horizon (target_date); compressing below a historical lag raises a "lag" cost flag (severity by how far below), tying rushed dates to the existing pricing mechanism. A missing lag table degrades loudly (explicit "no lag table" instruction), never a silent date. "Today" is rhetorical, exactly as the surveyor frames it — there is no stored present-date field. frontier_date is the SCOREABLE HORIZON (per world.ioa.toml and register_forecasts: claims resolving before it are registered), NOT the present, so it is deliberately not passed as "today" — doing so would collapse the timeline. Live repairers verify the current date via web search; hindcast repairers read the vantage from the corpus. Red-green TDD: lag_table_and_date_discipline_anchor_dates_to_history, hindcast_date_discipline_pins_the_vantage_not_the_clock, missing_lag_table_degrades_loudly_not_silently, empty_horizon_falls_back_to_a_descriptive_phrase. 12/12 host tests pass; spawn_repairers.wasm rebuilt. Live e2e exercise lands in the C6 flagship run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d PATCH
The dweller/story spine never completed end-to-end. Root cause, found on
run-1: animate_dwellers' DwellersCast handler persisted the dweller roster
with a raw PATCH /tdata/Worlds('...'), which Cedar denies (403 — only Admin
may PATCH a World; system flows drive it through actions). The handler
returned that error, the World transitioned to Failed (terminal), and the
three cast dwellers were orphaned with no traversal/story sessions. No
kind:story artifact had ever been produced because the flow died here.
Fix, entity-first: carry dweller_ids as a SpawnNextDweller action param
(exactly as check_count already rides), so the roster is persisted by the
permitted action transition rather than a raw field write. It is set on the
first tick (from DwellersCast) and persists across the omitting re-ticks, like
name/target_date/canonical_path_id. The denied PATCH is removed entirely.
- world.ioa.toml: SpawnNextDweller params ["check_count"] -> ["dweller_ids", "check_count"]
- model.csdl.xml: declare dweller_ids on the SpawnNextDweller action (the API
silently drops undeclared params, so the binding must list it)
- animate_dwellers: drop the PATCH; emit dweller_ids in the SpawnNextDweller result
Verified: corridor_engine_contract (12/12) and corridor_cedar_matrix (10/10)
pass; animate_dwellers host tests (6/6) pass; wasm rebuilt. Run-1's World is
terminally Failed and cannot host the re-drive, so the full dweller -> story ->
gate -> publish path is verified live in the next fresh run.
The phase_contradiction handler PATCHes Paths the same way but is non-fatal
(warns; the Reprice action carries the pricing feedback) — noted as a separate
follow-up, not on the story-spine critical path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… D0 liveness) The liveness fix (021ea51) lets a claim settle on its cheapest acceptable route past an in-flight straggler once the route budget is spent — but left that straggler self-healing. If the straggler can never complete (a permanently-broken session), its Repaired/Solving/Challenged state_timeout (ADR-007) re-spawns it forever on a claim that no longer needs it: the one unbounded self-heal left after settlement. Close it: when claim_phase settles, dispatch Path.Fail on every still-in-flight sibling route, marked "superseded" (not an error — the claim found its answer and the budget is spent). This fires only in the wedged-straggler case the liveness fix introduced; a live route that completes normally lands in `scored` and is classified Canonical/Tail/Reject as before. Now the search budget bounds the self-heal fully: a flapping route can delay a settle, never outlive it. 14/14 host tests pass; aggregate_costs.wasm rebuilt. Exercised live in the next fresh run (the HTTP settlement path is not unit-testable; claim_decision, which gates this, is). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ster_forecasts (D2)
D1: corridor_embed gains build_embed_request — the {"model","input":[...]} body
both Ollama /api/embed and OpenAI /v1/embeddings accept, so dev (local Ollama)
and prod (hosted) differ only by the endpoint/model config. The pure math/parse
core was already committed; this completes the fetch contract.
D2 reconcile (G1): register_forecasts no longer registers an authored EventNode
that merely restates an already-determined fact (the "first-party agents grow as
rivals" p=0.55 bug — a present truth dated to the future, resolving yes for free
and poisoning calibration). It embeds the determined reference + the registrable
authored candidates in one batch and collapses any candidate within a strict
cosine distance of a determined node, logging every decision with its distance
and the matched fact. Degrades to exact-text matching when no embedder is
reachable (never a silent pass). Endpoint/model from config with local-Ollama
defaults; module-gated Cedar egress already permits the call.
The threshold (RECONCILE_MAX_DISTANCE=0.10) is a tunable prior, now calibrated
live against mxbai-embed-large: domain restatements measure ~0.06 (collapse),
distinct forecasts ~0.29-0.35 (kept) — a wide clean margin. It is a conservative
backstop; the primary G1 fixes are upstream (web-grounded surveyor, repairer not
minting already-true facts), and embedding distance alone cannot tell "restates
a present fact" from "a future change to one", so the threshold stays strict.
Verified: corridor_embed 8/8, register_forecasts 10/10 host tests; both build to
wasm32 with corridor_embed linked; threshold validated against the running
embedder. Live in-corridor exercise lands in the next fresh run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR-005 records the grounding decisions shipped in D2: web-grounded surveyor present-state, lag-disciplined repairer dates, and the conservative embedding reconcile backstop in register_forecasts (with the calibrated 0.10 threshold and the strict-by-design rationale). ADR-006 records the embedding capability (corridor_embed, D1, implemented) and proposes the diversity design: named uncertainty axes on the sampling side (surprising-vs-consensus) plus an embedding diversity gate on the verification side (distinct-from-each-other). The gate needs a barrier; the World-barrier (A) vs per-endpoint (B) choice is left open for review — recommended (A), verified live in a flagship run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e corridor (D3) The first run's modal and anti-modal worlds converged (G2): distinct stances, near-identical futures, and the corridor then spent ~30 sessions each repairing two versions of the same world. ADR-006 option A closes this with a World-level barrier + embedding gate, so the budget only buys distinct worlds. Flow: the endpoint writer now self-reports BundleWritten (Endpoint Sampled -> Written, bundle recorded, NO decompose) instead of SubmitForRepair. On each BundleWritten, sample_endpoints runs the all-written barrier; once no endpoint is still Sampled it dispatches World.GateDiversity (round-counted). The gate embeds each Written world's bundle-head (mxbai caps at ~512 tokens, so a head, not the 30KB bundle; claims don't exist pre-decompose), greedily keeps the diverse ones (corridor_embed::select_diverse vs already-released references), releases them via Endpoint.SubmitForRepair (-> decompose -> repair), and re-steers collapsed ones via Endpoint.ReSteer (re-spawn the writer with a "diverge from <nearest world>" brief). After GATE_MAX_ROUNDS=2 a persistent near-duplicate is Discarded. DIVERSITY_MIN_DISTANCE=0.15 is a tunable prior (logged per round). If the embedder is unreachable the gate releases all worlds loudly rather than wedge the pass. Spec/CSDL/Cedar: new Endpoint state Written; actions BundleWritten (writer, session-relay), SubmitForRepair (now paramless, system — gate releases), ReSteer (system); Endpoint.revision_brief; World.gate_rounds + GateDiversity (system). SubmitForRepair moved from writer-self-report to system-dispatched. Tests: corridor_embed select_diverse (9/9), sample_endpoints prompt/contract (10/10), corridor_engine_contract (12/12), corridor_cedar_matrix (10/10); all wasm rebuilt. Gate orchestration (barrier/re-steer/release) verified live in the flagship run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
run-1b wedged: both endpoint writers ran, then their BundleWritten self-report was Cedar-denied (→ routed to the missing request_approval module → session Failed), leaving the endpoints stuck in Sampled with no bundles. Root cause: session self-reports arrive as the service:wasm-runtime relay principal, and the Endpoint relay permit still listed only the OLD writer action (SubmitForRepair). D3 renamed the writer's self-report to BundleWritten but added only the generic ["system","agent"] permit — which the relay principal (agent_type "service") does not match. Fix: the relay permit now grants BundleWritten + DecompositionComplete + Fail on Endpoint; SubmitForRepair and ReSteer are removed from it (the diversity gate dispatches those as system, never a session). Regression guard added to corridor_cedar_matrix::decomposition_and_edges_ride_the_relay: the relay grants BundleWritten and is denied SubmitForRepair/ReSteer. 10/10 matrix tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ng side)
run-1c proved the diversity GATE works but the SAMPLING was weak: generic
"anti-modal" stances ("85th percentile on the most load-bearing uncertainty")
converged onto the modal world, so budget-2 collapsed to a single distinct
world (the gate correctly discarded the duplicate, but it cannot manufacture
diversity the sampling never created). This is run-1's exact G2 failure.
Fix (ADR-006 sampling side): the surveyor now names the top 3-5 load-bearing
UNCERTAINTY AXES of the domain — the dimensions on which the future genuinely
forks — and the consensus pole of each, reported via SeedComplete and stored on
World.uncertainty_axes. driver_stance assigns each anti-modal world a DIFFERENT
named axis to invert (slot 0 modal; slot i inverts axes[i-1] against its
consensus pole), so the worlds are distinct by construction before the gate ever
runs. Falls back to the generic tail stance when there are fewer named axes than
budget slots. The gate remains the safety net for residual convergence.
- seed_world: surveyor names axes + reports uncertainty_axes in SeedComplete
- world.ioa.toml + CSDL: uncertainty_axes field + SeedComplete param
- sample_endpoints: parse_axes + axis-naming driver_stance(i, axes)
Tests: seed_world 6/6 (surveyor names axes), sample_endpoints 10/10
(stance_assignment_inverts_named_axes_and_falls_back), corridor_engine_contract
12/12, corridor_cedar_matrix 10/10. All wasm rebuilt. Live in the next run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… pace note Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ng needles (D4)
grade_hindcast matched actuals to forecasts by a case-insensitive substring
`.find`, which silently took the FIRST of several matches — order-sensitive and
fragile. It now matches by event_node_id exact first, then by nearest embedding
of the actual's descriptor to the forecast questions (deterministic, via
corridor_embed::nearest within MATCH_MAX_DISTANCE), and falls back to substring
only when the embedder is unreachable. Each embedding match logs its distance
for calibration. Endpoint/model from config with local-Ollama defaults; module-
gated Cedar egress already permits the call.
Note: the bundled fixtures use bare keyword needles ('gpt-5', 'price'), which
embed weakly; richer full-text actual descriptors would sharpen matching — a
fixture-data follow-up, separate from this matching mechanism.
Tests: grade_hindcast 9/9 (substring tests adapted to the index helper); wasm
rebuilt with corridor_embed linked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dor pace) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ies, grounded) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… calibration) run-1d exposed a gate-signal flaw: with named axes, the anti-modal world inverts ONE axis (autonomy) but holds consensus on the other four, so the two worlds' bundle-HEADS (shared dated-market retrospectives) measured only 0.111 apart — below the 0.15 threshold — and the gate re-steered a genuinely-forked world. Their SUMMARIES (the writers' one-line theses: "mature market" vs "policy-gated autonomous production") measured 0.298 apart, correctly distinct. So the gate now embeds the endpoint SUMMARY (which captures a world's thesis and divergence), falling back to the bundle-head only when a summary is missing. 0.298 > 0.15 keeps both named-axis worlds; near-identical summaries still re-steer. Simpler too — the summary is already on the endpoint (no bundle fetch). sample_endpoints 10/10 host tests pass; wasm rebuilt. Verified live next run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 2 distinct worlds Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…howcase needs clean env (2.7GB DB exhausted) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… collapses an unmeasurable world The summary-gate fix (6ab85c3) was necessary but not sufficient. phase_gate read each endpoint's Summary from the OData list projection, which lags the authoritative state BundleWritten had just committed. When the just-written endpoint showed an empty summary, the code fell back to the bundle-HEAD — the exact 0.11 false-collapse signal 6ab85c3 meant to retire. So distinct worlds were still collapsed under projection lag (run-1c en-019ec480: summaries 0.298 apart yet one Discarded; ec4d2: 0.254 apart yet one Discarded — both settled as single live worlds, defeating the diverse-portfolio guarantee). Fix: - phase_gate re-reads every endpoint authoritatively (fetch_entity), never the lagging list projection, for the gate decision. - Delete the bundle-head fallback (fetch_bundle_head + BUNDLE_HEAD_CHARS): the head is a known-bad signal and must never drive a discard. - New pure gate_decision(has_summary, diverse, rounds): a world we cannot yet measure (summary still absent after the authoritative read) is Released, never collapsed. Discard fires only past the re-steer round cap. Unit-proven (12/12 host tests incl. 2 new gate tests); wasm rebuilt (8360a1ef) and confirmed loaded. Live validation of the full diverse portfolio is blocked by local file-SQLite write saturation (2.7GB, "database is locked"); the logic is grounded in the forensics above + select_diverse's tested behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… restart) Captures verified deploy state mid-flight: prod openpaw up, Genesis registry, full bootstrap-refs string (bump only paw-foresight), publish-via-MCP steps, and the v0.1→corridor model-replacement decision. So the post-restart session (temper MCP repointed at openpaw) resumes without losing context. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-contained context dump for agent/operator takeover covering architecture, branches, progress, live blocker, and playbook.
Copy the active C0-C8 / D0-D4 plan into the repo as the canonical portable source (replacing ~/.claude/plans/ path). Update handoff §4, quick-reference, and read-next list accordingly.
Endpoint.Sampled sat in allow_indefinite_states with no state_timeout, so a writer session that died — or a server restart that orphaned its in-flight provider call — wedged the endpoint forever, and the diversity-gate barrier never fired because one sibling bundle was never written. Observed live on prod world en-019ed392 (2026-06-16): all 3 endpoint writers hung on their first model call; the run could only continue via hand re-spawn. Mirror Path's Solving/Repaired/Challenged self-heal: a 900s state_timeout on Sampled re-spawns the writer via a new ResumeWriter self-loop action, which shares phase_resteer with the gate's ReSteer (re-spawn with the endpoint's stance + any revision_brief). Budget sits well above a healthy writer (~3 min observed) so a live session is never double-spawned; a late duplicate's BundleWritten no-ops once the endpoint has left Sampled. - specs/endpoint.ioa.toml: drop Sampled from allow_indefinite_states; add the Sampled state_timeout and the ResumeWriter action. - wasm/sample_endpoints: route ReSteer | ResumeWriter -> phase_resteer. - policies/foresight.cedar: permit system-dispatched ResumeWriter on Endpoint. - corridor_engine_contract: pin Sampled self-heal (13 passing; +1). Host tests 12/12; contract suite 13/13. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… writer-stall root-caused First clean corridor run on prod Postgres (ref paw-foresight@7c19bf9). The diversity gate fired (gate_rounds=1) and released all 3 distinct worlds with 0 discards — the live proof of the e3543f7 authoritative-summary gate fix. Logs the writer-phase stall + recovery and the durable self-heal follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + lesson Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + run resumed via route nudge Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… API-guessing)
Adversary (and the other session-spawning modules') prompts said only "Write a
<thing> with temper.write" with no call signature or return shape, then asked
the agent to splice in "<file-id-from-temper.write>". A small fraction of agents
(observed ~2/200+ on prod world en-019ed392) reverse-engineered file creation
through temper.action against the Directories entity set
(Create/AddFile/CreateFile/Files.Create) — actions that don't exist on the
Directory automaton — tripping a Cedar implicit-deny -> request_approval ->
Session WaitingForApproval. A spawned agent has no chat channel, so no human
approves; it auto-resumes, re-guesses, and loops forever — the Path never
reaches ChallengeComplete and the owning Claim stays wedged in Bridging.
Class fix across all six session-spawning modules + five soul mirrors: replace
the bare token with an explicit, copyable recipe — result = temper.write("/<name>.md", "...")
returns {file_id, path, workspace_id}; use result["file_id"] — plus an explicit
prohibition on inventing a file/dir-creation API via temper.action (the
workspace already exists). No Cedar change: the temper.write path is already
permitted (system-relayed agent write); Directory file-create permits were
deliberately NOT added (those actions don't exist). Each prohibition is scoped
so temper.create("EventNodes"/"Artifacts") in repairer/surveyor/dweller/author
still works. Red-green prompt-contract test added per module; all six wasm
rebuilt. Host tests 55/0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dents, fixes, blocker Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Add World.Active ResumeWorldCascade and Endpoint.UnderRepair ResumeEndpointScoring state_timeouts with aggregate_costs handlers. Re-running world_cascade when all claims are terminal but canonical_path_id is unset prevents the en-019ed392 wedge; endpoint scoring self-heal covers missed ScoreComplete dispatches. Contract tests 3/3 pass.
|
Lane 3 merge-state diagnosis / safe path update GitHub's Root cause: PR #422's current head is the broad
So resolving #422 as-is would require broad searched-corridor reconciliation beyond Lane 3 and would touch/decide dirty-work-adjacent files. Safe mergeable shape tested in a temp clone instead:
Verification on that temp
I have not force-pushed this temp stack over Precise next choice needed:
Canonical target after Lane 4: Railway |
Summary
Lane 3 minimum no-wedge delta for ARN-65: add
World.Seeding -> ResumeSeedself-heal so a surveyor/bookmaker batch that never reportsSeedCompletecannot wedge the world inSeeding.This PR is against the active handoff branch/worktree
codex/searched-corridorand intentionally avoids the dirty user files:os-apps/paw-foresight/wasm/seed_world/src/lib.rsos-apps/paw-foresight/wasm/grade_hindcast/Cargo.lockos-apps/paw-foresight/wasm/register_forecasts/Cargo.lockDockerfile.foresight-patchrailway.foresight-patch.tomlChanges
crates/temperpaw/tests/corridor_engine_contract.rs: red/green contract for Seeding self-heal.os-apps/paw-foresight/specs/world.ioa.toml: removesSeedingfrom indefinite states, adds 1200sstate_timeouttoResumeSeed, and addsResumeSeedself-loop that triggersseed_world.os-apps/paw-foresight/specs/model.csdl.xml: exposes boundWorld.ResumeSeed.os-apps/paw-foresight/policies/foresight.cedar: permits system-onlyResumeSeedwith the other World self-heal actions.No ADR added: this is the last missing application of existing ADR-007 self-heal semantics to a single wait state, not a new architectural decision.
Evidence
Red:
9cd44847 test(foresight): pin seeding self-heal contractcargo test -p temperpaw --test corridor_engine_contract world_seeding_self_heals_when_surveyor_never_reports_seed_completefailed as expected:World.Seeding must not be indefinite.Green:
66129a71 fix(foresight): self-heal world seedingcargo test -p temperpaw --test corridor_engine_contract world_seeding_self_heals_when_surveyor_never_reports_seed_completepassed.cargo test -p temperpaw --test corridor_engine_contractpassed: 17/17.cargo build -p temperpawpassed.cargo build --manifest-path os-apps/paw-foresight/wasm/seed_world/Cargo.toml --target wasm32-unknown-unknown --release --lockedpassed and producedos-apps/paw-foresight/wasm/seed_world/target/wasm32-unknown-unknown/release/seed_world.wasm.git statusstill shows only the pre-existing dirty files listed above.Known caveat from dirty user work:
cargo test --manifest-path os-apps/paw-foresight/wasm/seed_world/Cargo.toml --lockedis currently red under the pre-existing dirtyseed_world/src/lib.rs: 6 passed, 1 failed (surveyor_prompt_carries_the_event_node_and_seed_complete_contract, missing exact"world_id": "w-1"prompt assertion). This PR did not edit that file.Live/canonical proof status
Read-only Railway discovery shows dedicated
foresightproduction service is runningghcr.io/nerdsane/temperpaw:sha-0271650athttps://foresight-production-72d1.up.railway.app, with newPostgres-v79yalso running. This PR is not deployed there yet.Unauthenticated readiness probe:
curl -fsS https://foresight-production-72d1.up.railway.app/readyzreturned ready.curl -fsS https://foresight-production-72d1.up.railway.app/paw/versionreturned 401, and this shell has noTEMPER_API_KEY/TEMPER_*live API credential.Canonical no-wedge e2e remains pending on deploy/install of this PR to the current canonical
foresighttarget and an authorized API key to runscripts/prove_corridor_e2e.pyagainst that target. Lane 3 is not taking the DB migration; it should coordinate with Lane 4 on the final DB target before running the proof.