diff --git a/notes/epiphany-core-harness-surfaces.md b/notes/epiphany-core-harness-surfaces.md index 99c60a52..d6f7c868 100644 --- a/notes/epiphany-core-harness-surfaces.md +++ b/notes/epiphany-core-harness-surfaces.md @@ -58,6 +58,7 @@ The rule is: | `thread/epiphany/context` | read-only reflection | landed, live-smoked | Targeted state shard for graph nodes/edges, active frontier, graph checkpoint, investigation checkpoint, observations, and evidence. | | `thread/epiphany/pressure` | read-only reflection | landed, live-smoked | Context-pressure gauge derived from token telemetry and recorded auto-compact/context limits. | | `thread/epiphany/reorient` | read-only policy reflection | landed, live-smoked | Bounded CRRC reorientation verdict derived from checkpoint, freshness, watcher, and pressure signals; returns resume vs regather without acting on the decision. | +| `thread/epiphany/crrc` | read-only coordinator reflection | landed, live-smoked | Composes pressure, reorientation verdict, bound worker status/result, and available actions into one recommendation without launching, accepting, compacting, or mutating state. | ## Write Authority @@ -84,6 +85,7 @@ The following must stay read-only: - `thread/epiphany/context` - `thread/epiphany/pressure` - `thread/epiphany/reorient` +- `thread/epiphany/crrc` - `thread/epiphany/reorientResult` `thread/epiphany/index` is a write to the retrieval catalog. It is not a @@ -91,7 +93,7 @@ license to mutate map/evidence/churn state as a side effect. ## Reflection Authority -Scene, jobs, freshness, context, reorient, and GUI surfaces may compress state for humans and +Scene, jobs, freshness, context, reorient, CRRC, and GUI surfaces may compress state for humans and clients. They may not invent canonical understanding. Reflection surfaces should: @@ -147,7 +149,7 @@ These are not landed yet: - typed turn intent before broad mutation - hard mutation gates for stale map or violated invariants - role-scoped specialist-agent registry and scheduling -- automatic runtime Compact-Rehydrate-Reorient-Continue coordination +- automatic runtime Compact-Rehydrate-Reorient-Continue execution beyond the landed read-only coordinator recommendation Do not implement these as one blob. Each needs a bounded surface, a write rule, and a verification story. @@ -178,10 +180,10 @@ Out of scope for the MVP: - a second job backend unless the current `agent_jobs` adapter blocks read-back or interruption -The first read-back and acceptance blockers are landed. The current MVP blocker -is bounded CRRC coordination: deciding when the harness should recommend or -request the landed pressure -> verdict -> launch -> result -> accept loop -without becoming an ambient scheduler. +The first read-back, acceptance, and read-only coordinator blockers are landed. +The current MVP blocker is dogfood visibility: exposing scene, pressure, +reorient, jobs, result, and CRRC recommendation in a small operator-facing view +that can be tested during real coding work without reading Rust. ## Job And Progress Surface Direction @@ -223,6 +225,14 @@ caller asks for those writes. It emits `thread/epiphany/stateUpdated` with source `reorientAccept`; it does not accept pending work, auto-promote arbitrary worker output, launch follow-up jobs, or silently continue implementation. +The first read-only coordinator over that loop is also landed as +`thread/epiphany/crrc`. It composes pressure, the resume-versus-regather +verdict, the fixed reorient-worker binding, result status/finding, and scene +actions into one recommendation such as continue, prepare checkpoint, launch +worker, wait for worker, review result, accept result, or regather manually. It +is not a scheduler, a hidden launch, a hidden accept, a compactor, or a second +source of truth. The little tyrant remains theoretical, for now. + The first live bound-runtime progress notification is also landed as `thread/epiphany/jobsUpdated`. It rides existing `agent_job_progress:{json}` background events from the runtime job runner, resolves matching launcher diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index f8efec25..cd65b02c 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -10,7 +10,7 @@ The important question for this file is not "what is Epiphany supposed to become Current Epiphany is a forked Codex harness with a typed modeling spine. -The spine exists in twenty-one live paths: +The spine exists in twenty-two live paths: - protocol state shape: `EpiphanyThreadState`, `RolloutItem::EpiphanyState`, retrieval summaries, and prompt tags live in [protocol.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/protocol/src/protocol.rs:103). - in-memory session state: `SessionState` stores an optional `EpiphanyThreadState`, and `Session` exposes thin async accessors over it in [state/session.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/state/session.rs:36) and [session/mod.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/mod.rs:1240). @@ -30,6 +30,7 @@ The spine exists in twenty-one live paths: - targeted context reflection: app-server declares experimental read-only `thread/epiphany/context`, reads the same live/stored Epiphany state view used by scene/jobs, and derives a bounded state shard containing selected graph nodes/edges, links, active frontier, checkpoint, the full durable investigation-checkpoint packet, observations, direct evidence, linked evidence, and missing requested ids without retrieval, proposal, promotion, notification, mutation, or persistence in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:355), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4253), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4261), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11152). - context-pressure reflection: core token telemetry carries `model_auto_compact_token_limit` beside total/last usage and `model_context_window`; app-server declares experimental read-only `thread/epiphany/pressure`, reads live token usage or the latest stored rollout `TokenCount`, and derives `unknown`/`low`/`elevated`/`high`/`critical` pressure plus a non-acting compaction-prep recommendation without mutating Epiphany state or scheduling compaction in [protocol.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/protocol/src/protocol.rs:2237), [turn_context.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/turn_context.rs:103), [session/mod.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/mod.rs:2716), [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:360), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4361), [token_usage_replay.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/token_usage_replay.rs:75), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4306), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11028). - reorientation policy: app-server declares experimental read-only `thread/epiphany/reorient`, reuses the same live/stored thread view plus mapped pressure/freshness/watcher signals, and derives a bounded `resume`/`regather` verdict with checkpoint-path/frontier reasons without mutating, compacting, scheduling, notifying, or continuing work in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:365), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4514), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4394), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11424). +- CRRC coordinator recommendation: app-server declares experimental read-only `thread/epiphany/crrc`, reuses the same pressure/freshness/watcher/reorientation mapping plus the fixed `reorient-worker` binding and result read-back helper, and returns one bounded recommendation such as continue, prepare checkpoint, launch worker, wait, review, accept, or regather manually without mutating, launching, accepting, compacting, notifying, scheduling, or silently continuing work in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:370), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4661), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4546). - reorientation launch: app-server now declares experimental `thread/epiphany/reorientLaunch`, requires a loaded thread plus durable investigation checkpoint, consumes the live reorientation verdict, and launches one fixed `reorient-worker` binding through the existing job-control seam with explicit resume-versus-regather scope, checkpoint-derived payload, and structured-output expectations instead of pretending CRRC suddenly grew a scheduler in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:370), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4575), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4519). - reorientation result read-back: app-server now declares experimental read-only `thread/epiphany/reorientResult`, defaults to the fixed `reorient-worker` binding, resolves that binding through the current `agent_jobs` backend when available, reads the single backend item result, and projects a reviewable finding with mode, summary, next safe move, checkpoint-validity flag, inspected files, frontier ids, evidence ids, raw result, and any job/item errors without mutating state, promoting evidence, or scheduling follow-up work in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:374), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4610), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4684). - reorientation finding acceptance: app-server now declares experimental `thread/epiphany/reorientAccept`, requires a loaded thread and a completed `reorient-worker` backend result, appends accepted observation/evidence records, optionally banks the finding into scratch and the durable investigation checkpoint, persists through the normal typed update path, and emits `thread/epiphany/stateUpdated` with source `reorientAccept` without accepting pending work, auto-promoting arbitrary worker output, launching follow-up work, or silently continuing implementation in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:378), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4635), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4885). @@ -82,6 +83,10 @@ flowchart TD QF --> QR["thread/epiphany/reorient derives read-only resume/regather verdict"] QQ --> QR QR --> Q + QR --> QCRRC["thread/epiphany/crrc recommends the next explicit CRRC action"] + QQ --> QCRRC + QJ --> QCRRC + QCRRC --> Q QR --> QRL["thread/epiphany/reorientLaunch can explicitly consume that verdict"] QRL --> QRLR["thread/epiphany/reorientResult reads back worker findings"] QRLR --> Q @@ -1320,6 +1325,7 @@ stored Epiphany state -> reflected as targeted read-only graph/evidence context shards for clients -> reflected as read-only context pressure from token telemetry -> reflected as a read-only resume/regather verdict from checkpoint + freshness + watcher + pressure +-> reflected as a read-only CRRC recommendation over verdict + worker/result state -> can be consumed explicitly by one bounded reorient-worker launch surface -> retrieval/indexing can inform future work -> explicit distillation can draft observation/evidence patches @@ -1329,7 +1335,7 @@ stored Epiphany state -> successful writes emit thread/epiphany/stateUpdated ``` -The current remaining missing organ is not the red pen, not the first chewing motion, not the first state-change bell, not the first client-shaped scene, not the first job/progress reflection, not the first live bound-job notification, not the first freshness lens, not the first watcher-backed invalidation read, not the first targeted graph/evidence context read, not the first context-pressure gauge, not the first durable investigation checkpoint packet, not the first bounded reorientation verdict, not the first thin launcher seam, not the first explicit launch/interrupt authority surface over that seam, not the first runtime consumer that can act on the reorientation verdict without becoming a hidden tyrant, not the first read-back surface for that worker's findings, and no longer the first explicit acceptance write for completed findings. Those exist. Proposal now has the first bits of map memory, focus, and selection hygiene: it can auto-select a bounded evidence-backed observation cluster when ids are omitted, requires accepting recent evidence behind selected observations, prioritizes stronger selected observations for proposal wording, reuses existing architecture nodes by concrete code-ref/path/id checks before creating new path nodes, can rescue unanchored graph nodes through strict unique semantic overlap, follows graph links and incident edges into the frontier, and reports match-kind-aware churn pressure from the actual proposal shape. Distillation now also has the first source-output-aware teeth for tool/model summaries. Promotion now notices risky deltas instead of just checking the shape of the envelope, treats graph expansion as risky even if the patch tries to mumble "low pressure" while dragging a new wall into the blueprint, token-matches strong verifier kinds so `contest` cannot sneak past as `test`, and validates investigation checkpoints against known evidence before the packet can be inked. Successful update/promote writes now emit `thread/epiphany/stateUpdated` with `source: "update"` or `source: "promote"`, event-level revision, and typed changed fields so clients no longer need to poll blindly, infer the cause from timing, or diff the whole state to know which top-level sections moved. Runtime `agent_job_progress` background events now also emit `thread/epiphany/jobsUpdated` with changed bound-job payloads so clients no longer need to poll `thread/epiphany/jobs` just to watch one real owner move, the bound jobs now reflect launcher id, authority scope, and backend kind/job id instead of treating raw runtime pointers as the architecture, `jobLaunch` / `jobInterrupt` can create or tear down those bindings without pretending reflection is authority, `reorientLaunch` can launch one fixed `reorient-worker` job from the checkpoint verdict without turning the verdict itself into a writer, `reorientResult` can read the worker's structured output back as a reviewable finding without promoting it, and `reorientAccept` can explicitly bank a completed finding into typed observation/evidence plus optional scratch/checkpoint state. The scene, jobs, freshness, context, pressure, reorient, and reorient-result surfaces give clients compact or bounded read-only views without making the GUI/client authoritative, scene/context surface the checkpoint packet back out without becoming writers, freshness surfaces watcher telemetry without becoming a hidden worker, and reorient now turns those signals into an explicit wakeup verdict that one explicit launch surface can consume on purpose. The remaining missing organs are a second backend if `agent_jobs` stops being enough, a bounded CRRC coordinator that decides when to use the landed launch/read-back/acceptance path, and eventually role-scoped specialist ownership without silently auto-promoting anything. +The current remaining missing organ is not the red pen, not the first chewing motion, not the first state-change bell, not the first client-shaped scene, not the first job/progress reflection, not the first live bound-job notification, not the first freshness lens, not the first watcher-backed invalidation read, not the first targeted graph/evidence context read, not the first context-pressure gauge, not the first durable investigation checkpoint packet, not the first bounded reorientation verdict, not the first thin launcher seam, not the first explicit launch/interrupt authority surface over that seam, not the first runtime consumer that can act on the reorientation verdict without becoming a hidden tyrant, not the first read-back surface for that worker's findings, not the first explicit acceptance write for completed findings, and no longer the first read-only CRRC coordinator recommendation. Those exist. Proposal now has the first bits of map memory, focus, and selection hygiene: it can auto-select a bounded evidence-backed observation cluster when ids are omitted, requires accepting recent evidence behind selected observations, prioritizes stronger selected observations for proposal wording, reuses existing architecture nodes by concrete code-ref/path/id checks before creating new path nodes, can rescue unanchored graph nodes through strict unique semantic overlap, follows graph links and incident edges into the frontier, and reports match-kind-aware churn pressure from the actual proposal shape. Distillation now also has the first source-output-aware teeth for tool/model summaries. Promotion now notices risky deltas instead of just checking the shape of the envelope, treats graph expansion as risky even if the patch tries to mumble "low pressure" while dragging a new wall into the blueprint, token-matches strong verifier kinds so `contest` cannot sneak past as `test`, and validates investigation checkpoints against known evidence before the packet can be inked. Successful update/promote writes now emit `thread/epiphany/stateUpdated` with `source: "update"` or `source: "promote"`, event-level revision, and typed changed fields so clients no longer need to poll blindly, infer the cause from timing, or diff the whole state to know which top-level sections moved. Runtime `agent_job_progress` background events now also emit `thread/epiphany/jobsUpdated` with changed bound-job payloads so clients no longer need to poll `thread/epiphany/jobs` just to watch one real owner move, the bound jobs now reflect launcher id, authority scope, and backend kind/job id instead of treating raw runtime pointers as the architecture, `jobLaunch` / `jobInterrupt` can create or tear down those bindings without pretending reflection is authority, `reorientLaunch` can launch one fixed `reorient-worker` job from the checkpoint verdict without turning the verdict itself into a writer, `reorientResult` can read the worker's structured output back as a reviewable finding without promoting it, `reorientAccept` can explicitly bank a completed finding into typed observation/evidence plus optional scratch/checkpoint state, and `crrc` can recommend the next explicit action over that loop without doing the action itself. The scene, jobs, freshness, context, pressure, reorient, crrc, and reorient-result surfaces give clients compact or bounded read-only views without making the GUI/client authoritative, scene/context surface the checkpoint packet back out without becoming writers, freshness surfaces watcher telemetry without becoming a hidden worker, reorient turns those signals into an explicit wakeup verdict that one explicit launch surface can consume on purpose, and crrc now tells the operator whether to continue, checkpoint, launch, wait, review, accept, or regather manually. The remaining missing organs are a second backend if `agent_jobs` stops being enough, a dogfood-facing operator view over the landed loop, and eventually role-scoped specialist ownership without silently auto-promoting anything. ```text model/tool observations @@ -1343,7 +1349,7 @@ model/tool observations The typed write path exists, the first deterministic observation/evidence distillation path exists, source-output-aware distillation now summarizes noisy tool/model output into typed evidence with final result/error/finished lines ahead of generic warnings, the first deterministic map/churn proposal path exists, proposal-quality hardening now auto-selects bounded observation clusters when ids are omitted, requires accepting recent evidence, reuses existing graph nodes by observed code refs, prioritizes stronger selected observations, rescues unanchored graph nodes by strict semantic overlap, focuses linked frontier context, reports match-kind-aware map-delta pressure, and the first verifier-backed promotion gate exists. The first map/churn safety layer also exists: promotion can reject state replacement patches that are not tied to explicit observations/evidence, that contain structurally broken subgoal, invariant, graph, frontier, checkpoint, investigation-checkpoint, or churn fields, that try to promote risky churn without a warning and stronger verifier evidence, that claim a graph expansion is low-pressure to dodge the warning gate, or that rely on substring verifier-kind accidents. The first live state notification layer also exists: accepted writes publish updated typed state plus event-level revision and typed changed fields; rejected promotions stay quiet. -In natural language: current Epiphany can preserve a map, show a map, project a compact scene from the map, project derived job/progress slots from the map and retrieval summary, reflect a thin launcher seam over bound work, explicitly launch or interrupt that bound work through a bounded authority surface, emit live bound-job progress notifications from runtime `agent_job_progress` events, project a read-only freshness lens over retrieval and graph staleness, layer watcher-backed invalidation telemetry onto that lens for loaded threads, project a targeted graph/evidence context shard from the map, project context pressure from token telemetry, bank an in-flight investigation packet in durable typed state, reflect that packet back into prompt/scene/context, turn those signals into a read-only resume-versus-regather verdict, explicitly launch one bounded `reorient-worker` job from that verdict, retrieve evidence for the map, draft one explicit observation/evidence patch, derive a bounded graph/frontier/churn candidate from verified evidence-backed observations with code refs, choose a small coherent observation set when the caller does not provide one, favor the strongest selected observation when drafting that candidate, reject or accept a verified candidate, apply explicit map/evidence/churn edits, notify clients after successful writes, and sanity-check those edits before they hit the ledger. It still does not invalidate stale graph regions automatically, reflect reorientation-worker results back into typed state, execute Epiphany-owned long-running jobs beyond the borrowed runtime `agent_jobs` backend, or coordinate specialist ownership. Teeth are installed; chewing is supervised; the first bell rings; the first wall board exists; the first shift board exists; the first freshness clipboard exists; the first door sensor now exists too; the first binder lens exists; the first pressure gauge exists; the first ember packet exists; the first verdict slip now exists too; the first bounded launch lever and stop switch now exist too; the first explicit wakeup-hand now exists too; the next slices are about safe waking and real ownership, not jawbones. +In natural language: current Epiphany can preserve a map, show a map, project a compact scene from the map, project derived job/progress slots from the map and retrieval summary, reflect a thin launcher seam over bound work, explicitly launch or interrupt that bound work through a bounded authority surface, emit live bound-job progress notifications from runtime `agent_job_progress` events, project a read-only freshness lens over retrieval and graph staleness, layer watcher-backed invalidation telemetry onto that lens for loaded threads, project a targeted graph/evidence context shard from the map, project context pressure from token telemetry, bank an in-flight investigation packet in durable typed state, reflect that packet back into prompt/scene/context, turn those signals into a read-only resume-versus-regather verdict, recommend the next explicit CRRC action over the verdict and worker/result state, explicitly launch one bounded `reorient-worker` job from that verdict, read its result back for review, explicitly accept a completed finding into typed state, retrieve evidence for the map, draft one explicit observation/evidence patch, derive a bounded graph/frontier/churn candidate from verified evidence-backed observations with code refs, choose a small coherent observation set when the caller does not provide one, favor the strongest selected observation when drafting that candidate, reject or accept a verified candidate, apply explicit map/evidence/churn edits, notify clients after successful writes, and sanity-check those edits before they hit the ledger. It still does not invalidate stale graph regions automatically, execute Epiphany-owned long-running jobs beyond the borrowed runtime `agent_jobs` backend, show the whole loop in a humane dogfood surface, or coordinate broad specialist ownership. Teeth are installed; chewing is supervised; the first bell rings; the first wall board exists; the first shift board exists; the first freshness clipboard exists; the first door sensor now exists too; the first binder lens exists; the first pressure gauge exists; the first ember packet exists; the first verdict slip now exists too; the first bounded launch lever and stop switch now exist too; the first explicit wakeup-hand now exists too; the first coordinator clipboard now exists too. The next slices are about making the wakeup legible and giving real roles real ownership, not jawbones. ## Verification Hooks diff --git a/notes/epiphany-fork-implementation-plan.md b/notes/epiphany-fork-implementation-plan.md index 9bbc6dcf..2de4c7fe 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -74,6 +74,7 @@ The landed machine now has: - bounded Phase 6 reorient-guided worker launch through `thread/epiphany/reorientLaunch` - read-only Phase 6 reorient-worker result read-back through `thread/epiphany/reorientResult` - explicit Phase 6 reorient-worker finding acceptance through `thread/epiphany/reorientAccept` +- read-only Phase 6 CRRC coordinator recommendation through `thread/epiphany/crrc` - live Phase 6 reorientation app-server smoke coverage in `tools/epiphany_phase6_reorient_smoke.py` - live Phase 6 reorient-launch app-server smoke coverage in `tools/epiphany_phase6_reorient_launch_smoke.py` - live Phase 6 job-control app-server smoke coverage in `tools/epiphany_phase6_job_control_smoke.py` @@ -100,6 +101,7 @@ These boundaries are more important than the individual method names: - `thread/epiphany/context` is a targeted reflection, not a state writer or hidden proposal engine. - `thread/epiphany/pressure` is a context-pressure reflection, not an automatic compactor, scheduler, or CRRC coordinator. - `thread/epiphany/reorient` is a bounded policy verdict, not an automatic runtime coordinator, scheduler, compactor, or hidden state writer. +- `thread/epiphany/crrc` is a read-only coordinator recommendation over existing signals, not a scheduler, launch button, acceptance gate, compactor, or hidden state writer. - The GUI may render and steer typed state, but it must not manufacture canonical understanding. - The app-server remains a host seam; Epiphany-owned machinery should live in `epiphany-core` where practical. - Qdrant is the preferred persistent semantic backend; BM25 remains the bootstrap/fallback/control path. @@ -136,9 +138,8 @@ and notify typed state. It can. The next unknowns are: -- how much automatic CRRC coordination belongs once pressure, verdict, launch, result read-back, and explicit acceptance all exist - how the landed watcher-backed invalidation telemetry should be consumed without turning freshness into a secret worker -- how much automatic CRRC coordination belongs in runtime once pressure, freshness, watcher, checkpoint, and reorientation signals all exist +- how far the read-only CRRC recommendation should go before explicit client/operator action takes over - whether future bounded CRRC consumers should keep reusing the landed `agent_jobs` backend through the explicit job-control seam or start defining a second backend contract - what Phase 6 should prove before specialist scheduling begins @@ -170,12 +171,12 @@ marketplace, broad ambient scheduling, automatic promotion of every tool observation, a GUI-first workflow, or a second job backend unless the current `agent_jobs` seam blocks the product loop. -The read-back and acceptance blockers are now landed as -`thread/epiphany/reorientResult` and `thread/epiphany/reorientAccept`. The next -MVP blocker is bounded CRRC coordination: the harness can measure pressure, -return a reorientation verdict, launch a worker, read its result, and explicitly -accept the finding, but it still lacks the small policy surface that decides -when to recommend or request that loop. +The read-back, acceptance, and coordinator blockers are now landed as +`thread/epiphany/reorientResult`, `thread/epiphany/reorientAccept`, and +`thread/epiphany/crrc`. The next MVP blocker is product dogfooding: a human can +now ask the harness what it believes and what it recommends, but the loop still +needs a small non-Rust-facing surface that makes that answer easy to inspect +and act on during real work. ## Phase 6 Direction @@ -183,7 +184,7 @@ Phase 6 should grow observable harness state outward from the typed spine. Useful candidates: -1. Decide how much bounded CRRC coordination belongs now that pressure, freshness, watcher, checkpoint, reorient, explicit job control, reorient launch, result read-back, and explicit acceptance all exist. +1. Build a minimal dogfood-facing view over scene, pressure, reorient, jobs, reorient result, and CRRC recommendation so a non-Rust-speaking operator can test the product loop. 2. Keep accepted worker findings review-gated; do not convert acceptance into automatic promotion of arbitrary worker output. 3. Add targeted scene/jobs/context/pressure/reorient fields only when a client or smoke exposes a real gap. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 74858a44..5b6be024 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -28,7 +28,7 @@ Do not trust this file for the exact live HEAD. Always check git. - Do not copy exact branch or HEAD from this note. Run `git status --short --branch` and `git log --oneline -5`. - Phase 1 through Phase 5 are complete enough. -- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/pressure`, `thread/epiphany/reorient`, and `thread/epiphany/reorientResult`; durable `jobBindings` now act as a thin Epiphany-owned launcher seam with launcher id, authority scope, and backend kind/job id, `thread/epiphany/jobLaunch` / `thread/epiphany/jobInterrupt` now provide explicit bounded authority over that seam, jobs can overlay it onto live runtime `agent_jobs` progress for loaded threads, and `thread/epiphany/jobsUpdated` now translates live `agent_job_progress:{json}` background events into changed launcher-bound notifications without polling or scheduling. Freshness carries watcher-backed invalidation inputs, reorient turns checkpoint plus freshness/pressure/watcher signals into a read-only resume-versus-regather verdict, `thread/epiphany/reorientLaunch` is the first explicit runtime consumer over that verdict, `thread/epiphany/reorientResult` reads the launched worker output back as a reviewable finding without mutation or promotion, and `thread/epiphany/reorientAccept` explicitly banks completed findings into accepted observation/evidence plus optional scratch/checkpoint state. +- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/pressure`, `thread/epiphany/reorient`, `thread/epiphany/crrc`, and `thread/epiphany/reorientResult`; durable `jobBindings` now act as a thin Epiphany-owned launcher seam with launcher id, authority scope, and backend kind/job id, `thread/epiphany/jobLaunch` / `thread/epiphany/jobInterrupt` now provide explicit bounded authority over that seam, jobs can overlay it onto live runtime `agent_jobs` progress for loaded threads, and `thread/epiphany/jobsUpdated` now translates live `agent_job_progress:{json}` background events into changed launcher-bound notifications without polling or scheduling. Freshness carries watcher-backed invalidation inputs, reorient turns checkpoint plus freshness/pressure/watcher signals into a read-only resume-versus-regather verdict, `thread/epiphany/reorientLaunch` is the first explicit runtime consumer over that verdict, `thread/epiphany/reorientResult` reads the launched worker output back as a reviewable finding without mutation or promotion, `thread/epiphany/reorientAccept` explicitly banks completed findings into accepted observation/evidence plus optional scratch/checkpoint state, and `thread/epiphany/crrc` recommends the next explicit CRRC action without launching, accepting, compacting, scheduling, or mutating. - Durable in-flight investigation checkpointing is now landed in authoritative typed state, writable through `thread/epiphany/update` or accepted `thread/epiphany/promote`, rendered into the prompt, and reflected through scene/context. - The repo is an Epiphany fork of Codex, not a Codex preset. - `vendor/codex` is tracked directly, not a submodule. @@ -66,6 +66,7 @@ The current spine: - bounded reorient-guided worker launch through `thread/epiphany/reorientLaunch` - read-only reorient-worker result read-back through `thread/epiphany/reorientResult` - explicit reorient-worker finding acceptance through `thread/epiphany/reorientAccept` +- read-only CRRC coordinator recommendation through `thread/epiphany/crrc` - thin launcher metadata in durable `jobBindings` - live bound-job progress notification through `thread/epiphany/jobsUpdated` - read-only compact reflection through `thread/epiphany/scene` @@ -74,6 +75,7 @@ The current spine: - read-only targeted state-shard reflection through `thread/epiphany/context` - read-only context-pressure reflection through `thread/epiphany/pressure` - read-only CRRC reorientation policy through `thread/epiphany/reorient` +- read-only CRRC next-action recommendation through `thread/epiphany/crrc` - durable investigation checkpoint packet through typed state, prompt, scene, and context - live scene app-server smoke through `tools/epiphany_phase6_scene_smoke.py` - live jobs app-server smoke through `tools/epiphany_phase6_jobs_smoke.py` @@ -99,6 +101,7 @@ The exact current control flow is documented in - `thread/epiphany/context` is read-only. - `thread/epiphany/pressure` is read-only. - `thread/epiphany/reorient` is read-only. +- `thread/epiphany/crrc` is read-only. - `thread/epiphany/reorientResult` is read-only. - Durable typed state writes go through `thread/epiphany/update`, accepted `thread/epiphany/promote`, `thread/epiphany/reorientAccept`, or the bounded `thread/epiphany/jobLaunch`, `thread/epiphany/jobInterrupt`, and `thread/epiphany/reorientLaunch` authority surfaces when they mutate `jobBindings`. - `thread/epiphany/index` writes the retrieval catalog, not durable Epiphany understanding. @@ -167,6 +170,10 @@ For the bounded reorient-guided worker launch surface, run: & 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' '.\tools\epiphany_phase6_reorient_launch_smoke.py' ``` +The same smoke now also covers the read-only `thread/epiphany/crrc` +coordinator recommendation over continue, wait, accept, interrupt, and relaunch +states. + For Codex Rust work on this Windows machine: ```powershell @@ -228,20 +235,21 @@ The bounded CRRC coordination/policy layer is now also landed as read-only watcher, and investigation-checkpoint signals to decide whether a checkpoint still deserves `resume` or has to admit `regather`. -Also keep the guardrail in mind: a read-only reorientation verdict is still not -automatic CRRC. Runtime coordination and next-action execution are still future -work even though read-only job ownership/progress reflection now has a real -runtime seam, the thin launcher boundary is landed, and one explicit -reorient-guided launch surface can now consume the verdict on purpose. +Also keep the guardrail in mind: a read-only reorientation verdict and read-only +CRRC recommendation are still not automatic CRRC execution. Runtime action is +still explicit even though read-only job ownership/progress reflection now has a +real runtime seam, the thin launcher boundary is landed, and one explicit +reorient-guided launch surface can consume the verdict on purpose. -When the user asks to continue, the next likely organ is bounded CRRC -coordination over the landed pressure, verdict, launch, result, and accept -loop. Keep it explicit and narrow; do not build a broad hidden scheduler. +When the user asks to continue, the next likely organ is a minimal dogfood-facing +operator view over the landed scene, pressure, reorient, jobs, result, and CRRC +recommendation loop. Keep it explicit and narrow; do not build a broad hidden +scheduler or GUI-as-source-of-truth. Live `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, -`thread/epiphany/pressure`, and `thread/epiphany/reorient` smokes are now -guardrails, not the next organs. +`thread/epiphany/pressure`, `thread/epiphany/reorient`, and +`thread/epiphany/crrc` smokes are now guardrails, not the next organs. ## Not Yet @@ -249,7 +257,7 @@ guardrails, not the next organs. - automatic observation promotion - specialist-agent scheduling - GUI-as-source-of-truth -- automatic runtime CRRC coordinator acting on the landed reorientation verdict and launch/read-back path +- automatic runtime CRRC execution acting on the landed recommendation/verdict and launch/read-back path - Epiphany-owned long-running job execution beyond the current runtime `agent_jobs` seam - broad event stream beyond the landed state update notification diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 7ef697c5..f0617d34 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -50,3 +50,4 @@ {"ts": "2026-04-26T16:30:40+00:00", "type": "verification", "status": "ok", "note": "Phase 6 thread/epiphany/reorientLaunch landed as the first bounded runtime consumer over the read-only reorientation verdict. It launches one fixed reorient-worker binding through the existing agent_jobs-backed job-control seam with explicit resume/regather scope, checkpoint-derived payload, and no automatic CRRC or hidden scheduler. Focused app-server-protocol thread_epiphany and schema fixture checks, app-server map_epiphany tests, cargo build -p codex-app-server, and tools/epiphany_phase6_scene_smoke.py, tools/epiphany_phase6_reorient_smoke.py, tools/epiphany_phase6_reorient_launch_smoke.py, and tools/epiphany_phase6_job_control_smoke.py passed."} {"ts": "2026-04-28T09:59:40+00:00", "type": "verification", "status": "ok", "note": "Phase 6 thread/epiphany/reorientResult landed as a read-only read-back surface for fixed or requested reorient-worker bindings. It resolves the current agent_jobs backend item and projects completed worker output as a reviewable finding with mode, summary, next safe move, checkpoint validity, inspected files, frontier/evidence ids, raw result, and errors without mutation, promotion, scheduling, or hidden continuation. Focused protocol/app-server tests, schema fixture regeneration/checks, app-server build, and tools/epiphany_phase6_reorient_launch_smoke.py passed with resume and regather result read-back assertions."} {"ts": "2026-04-28T10:27:16+00:00", "type": "verification", "status": "ok", "note": "Phase 6 thread/epiphany/reorientAccept landed as the explicit acceptance write after reorientResult. It requires a completed reorient-worker backend result, appends accepted observation/evidence records, optionally updates scratch and investigation checkpoint state, and emits stateUpdated with source reorientAccept without accepting pending work, auto-promoting arbitrary worker output, launching follow-up jobs, or silently continuing. Focused protocol/app-server tests, schema fixture regeneration/checks, app-server build, and tools/epiphany_phase6_reorient_launch_smoke.py passed with launch, read-back, and acceptance assertions."} +{"ts": "2026-04-28T11:01:36+00:00", "type": "implementation", "status": "ok", "note": "Landed read-only thread/epiphany/crrc coordinator recommendation over pressure, reorient verdict, fixed reorient-worker status/result, and available actions; focused protocol/app-server tests, schema fixtures, app-server build, cargo check, and live reorient-launch smoke verified the coordinator loop without adding scheduling or hidden mutation."} diff --git a/state/map.yaml b/state/map.yaml index 44cddd30..9b343967 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -12,8 +12,8 @@ objective: explicit typed models of the codebase, active goal, evidence, and change machinery they are working on. current: > - Grow Phase 6 reflection and observability from the landed typed state - spine without letting GUI/client state become authoritative. + Grow Phase 6 from the landed typed state spine toward a dogfoodable MVP + without letting GUI/client state become authoritative. constraints: - No frontier-scale training budget. @@ -53,15 +53,16 @@ data_flow: active_subgoals: - Treat Phase 5 update/distill/propose/promote/read/notification as complete enough unless a concrete regression appears. - - Preserve read-only boundaries for retrieve, distill, propose, scene, jobs, freshness, context, pressure, and reorient. + - Preserve read-only boundaries for retrieve, distill, propose, scene, jobs, freshness, context, pressure, reorient, crrc, and reorientResult. - Extend Phase 6 observability only where it reflects authoritative state or retrieval summaries without becoming a scheduler. + - Build the next operator-facing dogfood surface over landed Epiphany signals instead of adding invisible organs. - Keep persistent project state ruthlessly distilled so rehydration strengthens the next turn instead of bloating it. accepted_design: - Epiphany is a fork-level modeling architecture, not merely a Codex collaboration mode. - Authoritative Epiphany state lives in Codex protocol/core session and rollout state. - The prompt reads typed state through a bounded `` developer fragment. - - Clients read typed state through `Thread.epiphanyState`, compact reflections such as `thread/epiphany/scene`, `thread/epiphany/jobs`, and `thread/epiphany/freshness`, targeted state shards through `thread/epiphany/context`, runtime pressure through `thread/epiphany/pressure`, bounded CRRC reorientation verdicts through `thread/epiphany/reorient`, and live bound-job progress through `thread/epiphany/jobsUpdated`. + - Clients read typed state through `Thread.epiphanyState`, compact reflections such as `thread/epiphany/scene`, `thread/epiphany/jobs`, and `thread/epiphany/freshness`, targeted state shards through `thread/epiphany/context`, runtime pressure through `thread/epiphany/pressure`, bounded CRRC reorientation verdicts through `thread/epiphany/reorient`, read-only CRRC next-action recommendations through `thread/epiphany/crrc`, and live bound-job progress through `thread/epiphany/jobsUpdated`. - Durable typed state writes route through `thread/epiphany/update`, accepted `thread/epiphany/promote`, or the bounded `thread/epiphany/jobLaunch`, `thread/epiphany/jobInterrupt`, and `thread/epiphany/reorientLaunch` authority surfaces when they mutate `job_bindings`. - `thread/epiphany/retrieve`, `thread/epiphany/distill`, `thread/epiphany/propose`, `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/pressure`, and `thread/epiphany/reorient` stay read-only. - `thread/epiphany/index` writes the retrieval catalog, not durable Epiphany understanding. @@ -80,6 +81,7 @@ accepted_design: - `thread/epiphany/context` is the first targeted read-only state-shard surface: it selects graph nodes/edges, active frontier, checkpoint, observations, and evidence from authoritative typed state without mutating or notifying. - `thread/epiphany/pressure` is the first read-only context-pressure surface: it derives pressure from token telemetry and auto-compact/context limits without compacting, scheduling, mutating, or notifying. - `thread/epiphany/reorient` is the first bounded CRRC policy surface: it derives a read-only `resume` versus `regather` verdict from checkpoint, freshness, watcher, and pressure signals without mutating state, compacting, scheduling, or silently continuing work. + - `thread/epiphany/crrc` is the first bounded CRRC coordinator recommendation surface: it composes pressure, the reorientation verdict, fixed reorient-worker binding/result status, finding read-back, and available actions into one read-only recommendation without launching, accepting, compacting, scheduling, or mutating state. - `thread/epiphany/reorientLaunch` is the first bounded runtime consumer over the reorientation verdict: it launches one fixed `reorient-worker` job through the existing job-control seam with explicit resume-versus-regather scope, authority, and checkpoint-derived payload, without making `thread/epiphany/reorient` a writer or turning CRRC into an ambient scheduler. - `thread/epiphany/reorientResult` is the first bounded read-back surface for that worker: it reads the fixed or requested reorient-worker binding through the current `agent_jobs` backend and projects completed output as a reviewable finding without mutation, promotion, scheduling, or hidden continuation. - `thread/epiphany/reorientAccept` is the first explicit acceptance write for completed reorient-worker findings: it appends accepted observation/evidence records, can optionally update scratch and investigation checkpoint state, and emits `stateUpdated` with source `reorientAccept` without accepting pending work or launching follow-up work. @@ -100,7 +102,7 @@ landed_baseline: - Phase 3: typed client read surface through `Thread.epiphanyState`. - Phase 4: hybrid retrieval plus explicit Qdrant indexing with BM25 fallback. - Phase 5: typed update/distill/propose/promote/read/notification control plane. - - Phase 6: read-only scene, job/progress, freshness with watcher-backed invalidation inputs, targeted context, context-pressure, and reorientation policy surfaces, plus durable investigation checkpointing for compaction-safe planning, live bound-job progress notifications, a thin launcher seam in durable job bindings, explicit launch/interrupt authority over that seam, and a bounded reorient-guided worker launch surface. + - Phase 6: read-only scene, job/progress, freshness with watcher-backed invalidation inputs, targeted context, context-pressure, reorientation policy, and CRRC coordinator recommendation surfaces, plus durable investigation checkpointing for compaction-safe planning, live bound-job progress notifications, a thin launcher seam in durable job bindings, explicit launch/interrupt authority over that seam, and a bounded reorient-guided worker launch surface. - Phase 6 read-back: read-only reorient-worker result reflection through `thread/epiphany/reorientResult`. - Phase 6 acceptance: explicit reorient-worker finding acceptance through `thread/epiphany/reorientAccept`. guardrails: @@ -115,6 +117,7 @@ landed_baseline: - Use `tools/epiphany_phase6_reorient_smoke.py` before modifying bounded CRRC reorientation policy behavior. - Use `tools/epiphany_phase6_reorient_launch_smoke.py` before modifying the bounded reorient-guided worker launch surface. - Use `tools/epiphany_phase6_reorient_launch_smoke.py` before modifying the bounded reorient-worker result read-back surface. + - Use `tools/epiphany_phase6_reorient_launch_smoke.py` before modifying the read-only CRRC coordinator recommendation surface. - Use focused scene/jobs/context/pressure/reorient/protocol/schema checks for reflection-only changes. - Use `tools/epiphany_prepare_compaction.py` before and after imminent-compaction persistence passes. - Use `CARGO_TARGET_DIR=C:\Users\Meta\.cargo-target-codex` for Codex Rust work on this Windows machine. @@ -133,5 +136,5 @@ rejected_paths: reason: persistent state that accumulates every local truth becomes memory cruft and weakens rehydration current_status: - summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, freshness, targeted context, context-pressure reflection, watcher-backed invalidation inputs, durable investigation checkpointing, bounded CRRC reorientation policy, live bound-job progress notifications, a thin launcher seam in durable `job_bindings`, explicit `thread/epiphany/jobLaunch` / `thread/epiphany/jobInterrupt` authority over that seam, bounded `thread/epiphany/reorientLaunch`, read-only `thread/epiphany/reorientResult`, and explicit `thread/epiphany/reorientAccept` are landed. `job_bindings` now carry Epiphany-owned launcher metadata such as launcher id, authority scope, and backend kind/job id, while still tolerating the older runtime-agent-job pointer as a compatibility alias. `thread/epiphany/jobs` overlays that launcher seam onto real runtime `agent_jobs` progress for bound work, including runtime/backend job id, worker thread ids, and progress counts, without becoming a scheduler. `thread/epiphany/jobsUpdated` now translates runtime `agent_job_progress:{json}` background events into changed launcher-bound payloads only when the mapped job state actually changes, so clients can watch real owners move without polling the jobs read surface. The launch/interrupt control path still uses direct state-runtime fallback when a loaded ephemeral thread does not already carry a live sqlite handle, so authoritative bindings and runtime snapshots stay aligned instead of pretending the backend vanished. `thread/epiphany/reorient` consumes checkpoint, freshness, watcher, and pressure signals to return a read-only `resume` versus `regather` verdict with explicit checkpoint-path/frontier reasons. `thread/epiphany/reorientLaunch` explicitly launches one fixed `reorient-worker` job with resume-or-regather scope, checkpoint-derived payload, and bounded structured output expectations. `thread/epiphany/reorientResult` reads that worker output back from the current `agent_jobs` backend as a reviewable finding with mode, summary, next safe move, checkpoint validity, inspected files, frontier ids, evidence ids, raw result, and errors, without mutating state, promoting evidence, scheduling work, or silently continuing. `thread/epiphany/reorientAccept` requires a completed worker result, appends accepted observation/evidence records, can explicitly update scratch and investigation checkpoint state, and emits `stateUpdated` with source `reorientAccept` without accepting pending work, auto-promoting arbitrary output, or launching follow-up work. Focused protocol/app-server tests, schema fixture regeneration/checks, app-server build, and the live reorient-launch smoke cover the launch/read-back/accept seam. Investigation checkpoints still live in authoritative typed state, flow through the prompt, and reflect through scene/context without adding a second writer or fake scheduler. Persistent state was cut down so map, handoff, plan, harness surfaces, scratch, and evidence each carry one job instead of becoming a Jenga tower; evidence is now a distilled ledger rather than an activity feed. The global AGENTS Epiphany core command says persistent state is the agent's mind and explicitly permits the agent to ask for self-modification when memory or workflow makes it worse. The continuity goal is to bank the fire before the dark so compaction becomes less like death and more like sleep. - next_action: Continue toward the MVP by adding bounded CRRC coordination over the landed pressure, reorient verdict, launch, result read-back, and explicit acceptance loop. Automatic broad specialist scheduling remains future work; do not restart the completed pressure, freshness, watcher-invalidation, investigation-checkpoint, reorientation, jobs-binding, jobsUpdated, thin-launcher-seam, job-control, reorient-launch, reorient-result, or reorient-accept slice unless a concrete regression appears. + summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, freshness, targeted context, context-pressure reflection, watcher-backed invalidation inputs, durable investigation checkpointing, bounded CRRC reorientation policy, read-only CRRC coordinator recommendation, live bound-job progress notifications, a thin launcher seam in durable `job_bindings`, explicit `thread/epiphany/jobLaunch` / `thread/epiphany/jobInterrupt` authority over that seam, bounded `thread/epiphany/reorientLaunch`, read-only `thread/epiphany/reorientResult`, and explicit `thread/epiphany/reorientAccept` are landed. `job_bindings` now carry Epiphany-owned launcher metadata such as launcher id, authority scope, and backend kind/job id, while still tolerating the older runtime-agent-job pointer as a compatibility alias. `thread/epiphany/jobs` overlays that launcher seam onto real runtime `agent_jobs` progress for bound work, including runtime/backend job id, worker thread ids, and progress counts, without becoming a scheduler. `thread/epiphany/jobsUpdated` now translates runtime `agent_job_progress:{json}` background events into changed launcher-bound payloads only when the mapped job state actually changes, so clients can watch real owners move without polling the jobs read surface. The launch/interrupt control path still uses direct state-runtime fallback when a loaded ephemeral thread does not already carry a live sqlite handle, so authoritative bindings and runtime snapshots stay aligned instead of pretending the backend vanished. `thread/epiphany/reorient` consumes checkpoint, freshness, watcher, and pressure signals to return a read-only `resume` versus `regather` verdict with explicit checkpoint-path/frontier reasons. `thread/epiphany/crrc` composes pressure, that verdict, the fixed reorient-worker binding/result, finding read-back, and available actions into a read-only recommendation such as continue, prepare checkpoint, launch worker, wait, review, accept, or regather manually without launching, accepting, compacting, scheduling, or mutating. `thread/epiphany/reorientLaunch` explicitly launches one fixed `reorient-worker` job with resume-or-regather scope, checkpoint-derived payload, and bounded structured output expectations. `thread/epiphany/reorientResult` reads that worker output back from the current `agent_jobs` backend as a reviewable finding with mode, summary, next safe move, checkpoint validity, inspected files, frontier ids, evidence ids, raw result, and errors, without mutating state, promoting evidence, scheduling work, or silently continuing. `thread/epiphany/reorientAccept` requires a completed worker result, appends accepted observation/evidence records, can explicitly update scratch and investigation checkpoint state, and emits `stateUpdated` with source `reorientAccept` without accepting pending work, auto-promoting arbitrary output, or launching follow-up work. Focused protocol/app-server tests, schema fixture regeneration/checks, app-server build, and the live reorient-launch smoke cover the launch/read-back/accept/coordinator seam. Investigation checkpoints still live in authoritative typed state, flow through the prompt, and reflect through scene/context without adding a second writer or fake scheduler. Persistent state was cut down so map, handoff, plan, harness surfaces, scratch, and evidence each carry one job instead of becoming a Jenga tower; evidence is now a distilled ledger rather than an activity feed. The global AGENTS Epiphany core command says persistent state is the agent's mind and explicitly permits the agent to ask for self-modification when memory or workflow makes it worse. The continuity goal is to bank the fire before the dark so compaction becomes less like death and more like sleep. + next_action: Continue toward the MVP by adding a minimal dogfood-facing operator view over the landed scene, pressure, reorient, jobs, reorient-result, accept, and CRRC recommendation loop. Automatic broad specialist scheduling remains future work; do not restart the completed pressure, freshness, watcher-invalidation, investigation-checkpoint, reorientation, crrc, jobs-binding, jobsUpdated, thin-launcher-seam, job-control, reorient-launch, reorient-result, or reorient-accept slice unless a concrete regression appears. diff --git a/tools/epiphany_phase6_reorient_launch_smoke.py b/tools/epiphany_phase6_reorient_launch_smoke.py index a8476184..7841d91f 100644 --- a/tools/epiphany_phase6_reorient_launch_smoke.py +++ b/tools/epiphany_phase6_reorient_launch_smoke.py @@ -144,6 +144,23 @@ def assert_reorient_job( ) +def assert_crrc_recommendation( + crrc: dict[str, Any], + *, + action: str, + scene_action: str | None, +) -> None: + recommendation = crrc["recommendation"] + require( + recommendation["action"] == action, + f"CRRC should recommend {action}, got {recommendation!r}", + ) + require( + recommendation.get("recommendedSceneAction") == scene_action, + f"CRRC should point at scene action {scene_action!r}, got {recommendation!r}", + ) + + def wait_for_jobs_surface( client: AppServerClient, thread_id: str, @@ -206,6 +223,25 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: start_index=update_notification_start, ) + initial_crrc = client.send("thread/epiphany/crrc", {"threadId": thread_id}) + assert initial_crrc is not None + require(initial_crrc["source"] == "live", "CRRC should read live thread signals") + require(initial_crrc["stateStatus"] == "ready", "CRRC should see ready Epiphany state") + require(initial_crrc["stateRevision"] == 1, "CRRC should report revision 1") + require( + initial_crrc["decision"]["action"] == "resume", + "CRRC should preserve the clean resume verdict", + ) + require( + initial_crrc["reorientResultStatus"] == "missingBinding", + "CRRC should report the missing reorient binding before launch", + ) + assert_crrc_recommendation( + initial_crrc, + action="continue", + scene_action="reorient", + ) + resume_launch_notification_start = len(client.notifications) resume_launch = client.send( "thread/epiphany/reorientLaunch", @@ -269,6 +305,22 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: ) assert_reorient_job(job_by_id(resume_jobs["jobs"], BINDING_ID), action="resume") + resume_crrc = client.send("thread/epiphany/crrc", {"threadId": thread_id}) + assert resume_crrc is not None + require( + resume_crrc["reorientBindingId"] == BINDING_ID, + "CRRC should expose the fixed reorient binding", + ) + require( + resume_crrc["reorientResultStatus"] in {"pending", "running"}, + "CRRC should see the launched worker as in flight", + ) + assert_crrc_recommendation( + resume_crrc, + action="waitForReorientWorker", + scene_action="reorientResult", + ) + resume_result_payload = complete_reorient_backend_job( codex_home, resume_launch["job"]["backendJobId"], @@ -305,6 +357,22 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: "reorient result should project the next safe move", ) + resume_result_crrc = client.send("thread/epiphany/crrc", {"threadId": thread_id}) + assert resume_result_crrc is not None + require( + resume_result_crrc["reorientResultStatus"] == "completed", + "CRRC should see the completed resume result", + ) + require( + resume_result_crrc["reorientFinding"]["rawResult"] == resume_result_payload, + "CRRC should project the reviewable resume finding", + ) + assert_crrc_recommendation( + resume_result_crrc, + action="acceptReorientResult", + scene_action="reorientAccept", + ) + watched_file.write_text( "pub fn reorient_target() -> &'static str {\n \"after\"\n}\n", encoding="utf-8", @@ -315,6 +383,18 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: "touched checkpoint path should force a regather verdict", ) + drifted_crrc = client.send("thread/epiphany/crrc", {"threadId": thread_id}) + assert drifted_crrc is not None + require( + drifted_crrc["decision"]["action"] == "regather", + "CRRC should preserve the invalidated checkpoint verdict", + ) + assert_crrc_recommendation( + drifted_crrc, + action="acceptReorientResult", + scene_action="reorientAccept", + ) + interrupt_notification_start = len(client.notifications) interrupt = client.send( "thread/epiphany/jobInterrupt", @@ -338,6 +418,18 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: "jobs surface should reflect the interrupted revision", ) + interrupted_crrc = client.send("thread/epiphany/crrc", {"threadId": thread_id}) + assert interrupted_crrc is not None + require( + interrupted_crrc["reorientResultStatus"] == "backendUnavailable", + "CRRC should see the cleared backend after interruption", + ) + assert_crrc_recommendation( + interrupted_crrc, + action="launchReorientWorker", + scene_action="reorientLaunch", + ) + regather_launch_notification_start = len(client.notifications) regather_launch = client.send( "thread/epiphany/reorientLaunch", @@ -422,6 +514,22 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: "regather result should expose the raw worker result for review", ) + regather_result_crrc = client.send("thread/epiphany/crrc", {"threadId": thread_id}) + assert regather_result_crrc is not None + require( + regather_result_crrc["reorientResultStatus"] == "completed", + "CRRC should see the completed regather result", + ) + require( + regather_result_crrc["reorientFinding"]["rawResult"] == regather_result_payload, + "CRRC should expose the regather worker finding", + ) + assert_crrc_recommendation( + regather_result_crrc, + action="acceptReorientResult", + scene_action="reorientAccept", + ) + accept_notification_start = len(client.notifications) accepted = client.send( "thread/epiphany/reorientAccept", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 4a97b14e..ce94141a 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -15105,6 +15105,43 @@ ], "type": "string" }, + "ThreadEpiphanyCrrcAction": { + "enum": [ + "continue", + "prepareCheckpoint", + "launchReorientWorker", + "waitForReorientWorker", + "reviewReorientResult", + "acceptReorientResult", + "regatherManually" + ], + "type": "string" + }, + "ThreadEpiphanyCrrcRecommendation": { + "properties": { + "action": { + "$ref": "#/definitions/v2/ThreadEpiphanyCrrcAction" + }, + "reason": { + "type": "string" + }, + "recommendedSceneAction": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadEpiphanySceneAction" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "action", + "reason" + ], + "type": "object" + }, "ThreadEpiphanyFreshnessSource": { "enum": [ "stored", @@ -16062,6 +16099,7 @@ "freshness", "pressure", "reorient", + "crrc", "reorientLaunch", "reorientResult", "reorientAccept", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 23c7df78..a2620e19 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/vendor/codex/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -12992,6 +12992,43 @@ ], "type": "string" }, + "ThreadEpiphanyCrrcAction": { + "enum": [ + "continue", + "prepareCheckpoint", + "launchReorientWorker", + "waitForReorientWorker", + "reviewReorientResult", + "acceptReorientResult", + "regatherManually" + ], + "type": "string" + }, + "ThreadEpiphanyCrrcRecommendation": { + "properties": { + "action": { + "$ref": "#/definitions/ThreadEpiphanyCrrcAction" + }, + "reason": { + "type": "string" + }, + "recommendedSceneAction": { + "anyOf": [ + { + "$ref": "#/definitions/ThreadEpiphanySceneAction" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "action", + "reason" + ], + "type": "object" + }, "ThreadEpiphanyFreshnessSource": { "enum": [ "stored", @@ -13949,6 +13986,7 @@ "freshness", "pressure", "reorient", + "crrc", "reorientLaunch", "reorientResult", "reorientAccept", diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyCrrcAction.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyCrrcAction.ts new file mode 100644 index 00000000..77e743e0 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyCrrcAction.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ThreadEpiphanyCrrcAction = "continue" | "prepareCheckpoint" | "launchReorientWorker" | "waitForReorientWorker" | "reviewReorientResult" | "acceptReorientResult" | "regatherManually"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyCrrcRecommendation.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyCrrcRecommendation.ts new file mode 100644 index 00000000..c2461d11 --- /dev/null +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanyCrrcRecommendation.ts @@ -0,0 +1,7 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ThreadEpiphanyCrrcAction } from "./ThreadEpiphanyCrrcAction"; +import type { ThreadEpiphanySceneAction } from "./ThreadEpiphanySceneAction"; + +export type ThreadEpiphanyCrrcRecommendation = { action: ThreadEpiphanyCrrcAction, recommendedSceneAction?: ThreadEpiphanySceneAction | null, reason: string, }; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneAction.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneAction.ts index a13fd132..d1a639f0 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneAction.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/ThreadEpiphanySceneAction.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type ThreadEpiphanySceneAction = "index" | "retrieve" | "distill" | "context" | "jobs" | "jobLaunch" | "jobInterrupt" | "freshness" | "pressure" | "reorient" | "reorientLaunch" | "reorientResult" | "reorientAccept" | "propose" | "promote" | "update"; +export type ThreadEpiphanySceneAction = "index" | "retrieve" | "distill" | "context" | "jobs" | "jobLaunch" | "jobInterrupt" | "freshness" | "pressure" | "reorient" | "crrc" | "reorientLaunch" | "reorientResult" | "reorientAccept" | "propose" | "promote" | "update"; diff --git a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index d0515e89..30bdc72c 100644 --- a/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/vendor/codex/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -321,6 +321,8 @@ export type { ThreadEpiphanyContext } from "./ThreadEpiphanyContext"; export type { ThreadEpiphanyContextMissing } from "./ThreadEpiphanyContextMissing"; export type { ThreadEpiphanyContextSource } from "./ThreadEpiphanyContextSource"; export type { ThreadEpiphanyContextStateStatus } from "./ThreadEpiphanyContextStateStatus"; +export type { ThreadEpiphanyCrrcAction } from "./ThreadEpiphanyCrrcAction"; +export type { ThreadEpiphanyCrrcRecommendation } from "./ThreadEpiphanyCrrcRecommendation"; export type { ThreadEpiphanyFreshnessSource } from "./ThreadEpiphanyFreshnessSource"; export type { ThreadEpiphanyGraphContext } from "./ThreadEpiphanyGraphContext"; export type { ThreadEpiphanyGraphFreshness } from "./ThreadEpiphanyGraphFreshness"; diff --git a/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs b/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs index 2a0b0701..f7d0ccac 100644 --- a/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs @@ -366,6 +366,11 @@ client_request_definitions! { params: v2::ThreadEpiphanyReorientParams, response: v2::ThreadEpiphanyReorientResponse, }, + #[experimental("thread/epiphany/crrc")] + ThreadEpiphanyCrrc => "thread/epiphany/crrc" { + params: v2::ThreadEpiphanyCrrcParams, + response: v2::ThreadEpiphanyCrrcResponse, + }, #[experimental("thread/epiphany/reorientLaunch")] ThreadEpiphanyReorientLaunch => "thread/epiphany/reorientLaunch" { params: v2::ThreadEpiphanyReorientLaunchParams, @@ -1725,6 +1730,7 @@ mod tests { v2::ThreadEpiphanySceneAction::Freshness, v2::ThreadEpiphanySceneAction::Pressure, v2::ThreadEpiphanySceneAction::Reorient, + v2::ThreadEpiphanySceneAction::Crrc, v2::ThreadEpiphanySceneAction::ReorientLaunch, v2::ThreadEpiphanySceneAction::Update, v2::ThreadEpiphanySceneAction::JobInterrupt, @@ -1825,6 +1831,7 @@ mod tests { "freshness", "pressure", "reorient", + "crrc", "reorientLaunch", "update", "jobInterrupt", @@ -2242,6 +2249,117 @@ mod tests { Ok(()) } + #[test] + fn serialize_thread_epiphany_crrc_response() -> Result<()> { + let response = ClientResponse::ThreadEpiphanyCrrc { + request_id: RequestId::Integer(9), + response: v2::ThreadEpiphanyCrrcResponse { + thread_id: "thr_123".to_string(), + source: v2::ThreadEpiphanyReorientSource::Live, + state_status: v2::ThreadEpiphanyReorientStateStatus::Ready, + state_revision: Some(4), + pressure: v2::ThreadEpiphanyPressure { + status: v2::ThreadEpiphanyPressureStatus::Ready, + level: v2::ThreadEpiphanyPressureLevel::High, + basis: v2::ThreadEpiphanyPressureBasis::AutoCompactLimit, + used_tokens: Some(92_000), + model_context_window: Some(128_000), + model_auto_compact_token_limit: Some(100_000), + remaining_tokens: Some(8_000), + ratio_per_mille: Some(920), + should_prepare_compaction: true, + note: "Pressure is derived from the model auto-compact token limit." + .to_string(), + }, + decision: v2::ThreadEpiphanyReorientDecision { + action: v2::ThreadEpiphanyReorientAction::Regather, + checkpoint_status: v2::ThreadEpiphanyReorientCheckpointStatus::ResumeReady, + checkpoint_id: Some("ix-1".to_string()), + pressure_level: v2::ThreadEpiphanyPressureLevel::High, + retrieval_status: v2::ThreadEpiphanyRetrievalFreshnessStatus::Stale, + graph_status: v2::ThreadEpiphanyGraphFreshnessStatus::Ready, + watcher_status: v2::ThreadEpiphanyInvalidationStatus::Changed, + reasons: vec![v2::ThreadEpiphanyReorientReason::CheckpointPathsChanged], + checkpoint_dirty_paths: Vec::new(), + checkpoint_changed_paths: vec![PathBuf::from("src/lib.rs")], + active_frontier_node_ids: vec!["state-spine".to_string()], + next_action: "Re-gather source before editing.".to_string(), + note: "Resume-ready checkpoint was invalidated by watcher/frontier drift." + .to_string(), + }, + recommendation: v2::ThreadEpiphanyCrrcRecommendation { + action: v2::ThreadEpiphanyCrrcAction::LaunchReorientWorker, + recommended_scene_action: Some(v2::ThreadEpiphanySceneAction::ReorientLaunch), + reason: "The current pressure/reorientation verdict needs a bounded worker before safe continuation." + .to_string(), + }, + reorient_binding_id: "reorient-worker".to_string(), + reorient_result_status: v2::ThreadEpiphanyReorientResultStatus::MissingBinding, + reorient_job: None, + reorient_finding: None, + available_actions: vec![ + v2::ThreadEpiphanySceneAction::Reorient, + v2::ThreadEpiphanySceneAction::Crrc, + v2::ThreadEpiphanySceneAction::ReorientLaunch, + ], + note: "The current pressure/reorientation verdict needs a bounded worker before safe continuation. Result status: MissingBinding. No matching Epiphany reorientation worker binding exists." + .to_string(), + }, + }; + + assert_eq!(response.id(), &RequestId::Integer(9)); + assert_eq!(response.method(), "thread/epiphany/crrc"); + assert_eq!( + json!({ + "method": "thread/epiphany/crrc", + "id": 9, + "response": { + "threadId": "thr_123", + "source": "live", + "stateStatus": "ready", + "stateRevision": 4, + "pressure": { + "status": "ready", + "level": "high", + "basis": "autoCompactLimit", + "usedTokens": 92000, + "modelContextWindow": 128000, + "modelAutoCompactTokenLimit": 100000, + "remainingTokens": 8000, + "ratioPerMille": 920, + "shouldPrepareCompaction": true, + "note": "Pressure is derived from the model auto-compact token limit." + }, + "decision": { + "action": "regather", + "checkpointStatus": "resumeReady", + "checkpointId": "ix-1", + "pressureLevel": "high", + "retrievalStatus": "stale", + "graphStatus": "ready", + "watcherStatus": "changed", + "reasons": ["checkpointPathsChanged"], + "checkpointChangedPaths": ["src/lib.rs"], + "activeFrontierNodeIds": ["state-spine"], + "nextAction": "Re-gather source before editing.", + "note": "Resume-ready checkpoint was invalidated by watcher/frontier drift." + }, + "recommendation": { + "action": "launchReorientWorker", + "recommendedSceneAction": "reorientLaunch", + "reason": "The current pressure/reorientation verdict needs a bounded worker before safe continuation." + }, + "reorientBindingId": "reorient-worker", + "reorientResultStatus": "missingBinding", + "availableActions": ["reorient", "crrc", "reorientLaunch"], + "note": "The current pressure/reorientation verdict needs a bounded worker before safe continuation. Result status: MissingBinding. No matching Epiphany reorientation worker binding exists." + } + }), + serde_json::to_value(&response)?, + ); + Ok(()) + } + #[test] fn serialize_thread_epiphany_reorient_launch_response() -> Result<()> { let response = ClientResponse::ThreadEpiphanyReorientLaunch { @@ -3816,6 +3934,18 @@ mod tests { assert_eq!(reason, Some("thread/epiphany/reorient")); } + #[test] + fn thread_epiphany_crrc_is_marked_experimental() { + let request = ClientRequest::ThreadEpiphanyCrrc { + request_id: RequestId::Integer(1), + params: v2::ThreadEpiphanyCrrcParams { + thread_id: "thr_123".to_string(), + }, + }; + let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request); + assert_eq!(reason, Some("thread/epiphany/crrc")); + } + #[test] fn thread_epiphany_reorient_launch_is_marked_experimental() { let request = ClientRequest::ThreadEpiphanyReorientLaunch { diff --git a/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs b/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs index bd493258..a20b24c9 100644 --- a/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -4002,6 +4002,7 @@ pub enum ThreadEpiphanySceneAction { Freshness, Pressure, Reorient, + Crrc, ReorientLaunch, ReorientResult, ReorientAccept, @@ -4654,6 +4655,63 @@ pub struct ThreadEpiphanyReorientAcceptResponse { pub finding: ThreadEpiphanyReorientFinding, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadEpiphanyCrrcParams { + pub thread_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadEpiphanyCrrcResponse { + pub thread_id: String, + pub source: ThreadEpiphanyReorientSource, + pub state_status: ThreadEpiphanyReorientStateStatus, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional = nullable)] + pub state_revision: Option, + pub pressure: ThreadEpiphanyPressure, + pub decision: ThreadEpiphanyReorientDecision, + pub recommendation: ThreadEpiphanyCrrcRecommendation, + pub reorient_binding_id: String, + pub reorient_result_status: ThreadEpiphanyReorientResultStatus, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional = nullable)] + pub reorient_job: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional = nullable)] + pub reorient_finding: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub available_actions: Vec, + pub note: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadEpiphanyCrrcRecommendation { + pub action: ThreadEpiphanyCrrcAction, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional = nullable)] + pub recommended_scene_action: Option, + pub reason: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub enum ThreadEpiphanyCrrcAction { + Continue, + PrepareCheckpoint, + LaunchReorientWorker, + WaitForReorientWorker, + ReviewReorientResult, + AcceptReorientResult, + RegatherManually, +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs b/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs index c19d8952..6626e1fc 100644 --- a/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs +++ b/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs @@ -154,6 +154,10 @@ use codex_app_server_protocol::ThreadEpiphanyContextParams; use codex_app_server_protocol::ThreadEpiphanyContextResponse; use codex_app_server_protocol::ThreadEpiphanyContextSource; use codex_app_server_protocol::ThreadEpiphanyContextStateStatus; +use codex_app_server_protocol::ThreadEpiphanyCrrcAction; +use codex_app_server_protocol::ThreadEpiphanyCrrcParams; +use codex_app_server_protocol::ThreadEpiphanyCrrcRecommendation; +use codex_app_server_protocol::ThreadEpiphanyCrrcResponse; use codex_app_server_protocol::ThreadEpiphanyDistillParams; use codex_app_server_protocol::ThreadEpiphanyDistillResponse; use codex_app_server_protocol::ThreadEpiphanyFreshnessParams; @@ -1098,6 +1102,10 @@ impl CodexMessageProcessor { self.thread_epiphany_reorient(to_connection_request_id(request_id), params) .await; } + ClientRequest::ThreadEpiphanyCrrc { request_id, params } => { + self.thread_epiphany_crrc(to_connection_request_id(request_id), params) + .await; + } ClientRequest::ThreadEpiphanyReorientLaunch { request_id, params } => { self.thread_epiphany_reorient_launch(to_connection_request_id(request_id), params) .await; @@ -4535,6 +4543,155 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } + async fn thread_epiphany_crrc( + &self, + request_id: ConnectionRequestId, + params: ThreadEpiphanyCrrcParams, + ) { + let ThreadEpiphanyCrrcParams { thread_id } = params; + + let thread_uuid = match ThreadId::from_string(&thread_id) { + Ok(id) => id, + Err(err) => { + self.send_invalid_request_error(request_id, format!("invalid thread id: {err}")) + .await; + return; + } + }; + + let loaded_thread = self.thread_manager.get_thread(thread_uuid).await.ok(); + let loaded = loaded_thread.is_some(); + let thread = match self.read_thread_view(thread_uuid, false).await { + Ok(thread) => thread, + Err(ThreadReadViewError::InvalidRequest(message)) => { + self.send_invalid_request_error(request_id, message).await; + return; + } + Err(ThreadReadViewError::Internal(message)) => { + self.send_internal_error(request_id, message).await; + return; + } + }; + + let retrieval_override = if let Some(loaded_thread) = loaded_thread.as_ref() { + Some(loaded_thread.epiphany_retrieval_state().await) + } else { + None + }; + let watcher_snapshot = if let Some(loaded_thread) = loaded_thread.as_ref() { + let config_snapshot = loaded_thread.config_snapshot().await; + self.epiphany_invalidation_manager + .ensure_thread_watch(&thread_id, &config_snapshot.cwd) + .await; + Some( + self.epiphany_invalidation_manager + .snapshot(&thread_id) + .await, + ) + } else { + None + }; + let (source, token_usage_info) = if let Some(loaded_thread) = loaded_thread.as_ref() { + ( + ThreadEpiphanyReorientSource::Live, + loaded_thread.token_usage_info().await, + ) + } else { + let token_usage_info = match thread.path.as_deref() { + Some(path) => latest_token_usage_info_from_rollout_path(path).await, + None => None, + }; + (ThreadEpiphanyReorientSource::Stored, token_usage_info) + }; + + let (state_revision, retrieval, graph, watcher) = map_epiphany_freshness( + thread.epiphany_state.as_ref(), + retrieval_override.as_ref(), + watcher_snapshot.as_ref(), + ); + let pressure = map_epiphany_pressure(token_usage_info.as_ref()); + let (state_status, decision) = map_epiphany_reorient( + thread.epiphany_state.as_ref(), + &pressure, + &retrieval, + &graph, + &watcher, + ); + + let mut state_db_ctx = if let Some(loaded_thread) = loaded_thread.as_ref() { + loaded_thread.epiphany_state_runtime().await + } else { + None + }; + if state_db_ctx.is_none() { + state_db_ctx = open_state_db_for_direct_thread_lookup(&self.config).await; + } + let job_resolution = load_epiphany_job_launcher_snapshots( + state_db_ctx.as_ref(), + thread.epiphany_state.as_ref(), + ) + .await; + let jobs = map_epiphany_jobs( + thread.epiphany_state.as_ref(), + retrieval_override.as_ref(), + &job_resolution, + ); + let reorient_job = jobs + .iter() + .find(|job| job.id == EPIPHANY_REORIENT_LAUNCH_BINDING_ID) + .cloned(); + let (result_status, finding, result_note) = load_epiphany_reorient_result_snapshot( + thread.epiphany_state.as_ref(), + state_db_ctx.as_ref(), + EPIPHANY_REORIENT_LAUNCH_BINDING_ID, + ) + .await; + let available_actions = epiphany_scene_available_actions( + loaded, + thread.epiphany_state.is_some(), + thread + .epiphany_state + .as_ref() + .and_then(|state| state.investigation_checkpoint.as_ref()) + .is_some(), + reorient_job.is_some(), + ); + let recommendation = map_epiphany_crrc_recommendation( + loaded, + state_status, + &pressure, + &decision, + result_status, + thread + .epiphany_state + .as_ref() + .and_then(|state| state.investigation_checkpoint.as_ref()) + .is_some(), + finding.is_some(), + ); + let note = format!( + "{} Result status: {:?}. {}", + recommendation.reason, result_status, result_note + ); + + let response = ThreadEpiphanyCrrcResponse { + thread_id, + source, + state_status, + state_revision, + pressure, + decision, + recommendation, + reorient_binding_id: EPIPHANY_REORIENT_LAUNCH_BINDING_ID.to_string(), + reorient_result_status: result_status, + reorient_job, + reorient_finding: finding, + available_actions, + note, + }; + self.outgoing.send_response(request_id, response).await; + } + async fn thread_epiphany_reorient_launch( &self, request_id: ConnectionRequestId, @@ -11909,6 +12066,7 @@ fn epiphany_scene_available_actions( ThreadEpiphanySceneAction::Freshness, ThreadEpiphanySceneAction::Pressure, ThreadEpiphanySceneAction::Reorient, + ThreadEpiphanySceneAction::Crrc, ]; if checkpoint_present { actions.push(ThreadEpiphanySceneAction::ReorientLaunch); @@ -12150,6 +12308,201 @@ fn render_epiphany_reorient_result_note( } } +async fn load_epiphany_reorient_result_snapshot( + state: Option<&EpiphanyThreadState>, + state_db_ctx: Option<&StateDbHandle>, + binding_id: &str, +) -> ( + ThreadEpiphanyReorientResultStatus, + Option, + String, +) { + let Some(state) = state else { + return ( + ThreadEpiphanyReorientResultStatus::MissingState, + None, + "No authoritative Epiphany state exists for this thread.".to_string(), + ); + }; + let Some(binding) = state + .job_bindings + .iter() + .find(|binding| binding.id == binding_id) + else { + return ( + ThreadEpiphanyReorientResultStatus::MissingBinding, + None, + "No matching Epiphany reorientation worker binding exists.".to_string(), + ); + }; + let Some(agent_job_id) = binding_agent_jobs_job_id(binding) else { + return ( + ThreadEpiphanyReorientResultStatus::BackendUnavailable, + None, + "The matching binding is not currently backed by the agent_jobs backend.".to_string(), + ); + }; + let Some(state_db_ctx) = state_db_ctx else { + return ( + ThreadEpiphanyReorientResultStatus::BackendUnavailable, + None, + "State runtime is unavailable, so the bound agent_jobs result cannot be read." + .to_string(), + ); + }; + let agent_job = match state_db_ctx.get_agent_job(agent_job_id).await { + Ok(Some(job)) => job, + Ok(None) => { + return ( + ThreadEpiphanyReorientResultStatus::BackendMissing, + None, + format!( + "Bound agent_jobs backend job {:?} was not found.", + agent_job_id + ), + ); + } + Err(err) => { + return ( + ThreadEpiphanyReorientResultStatus::BackendUnavailable, + None, + format!("Failed to read Epiphany reorientation backend job: {err}"), + ); + } + }; + let item = match state_db_ctx + .get_agent_job_item(agent_job_id, binding_id) + .await + { + Ok(Some(item)) => item, + Ok(None) => { + return ( + ThreadEpiphanyReorientResultStatus::BackendMissing, + None, + "Bound agent_jobs backend item was not found for this reorient worker.".to_string(), + ); + } + Err(err) => { + return ( + ThreadEpiphanyReorientResultStatus::BackendUnavailable, + None, + format!("Failed to read Epiphany reorientation backend item: {err}"), + ); + } + }; + let status = map_epiphany_reorient_result_status(agent_job.status, item.status); + let finding = item.result_json.as_ref().map(|result| { + map_epiphany_reorient_finding( + result.clone(), + agent_job.last_error.clone(), + item.last_error.clone(), + ) + }); + let note = + render_epiphany_reorient_result_note(status, finding.as_ref(), item.last_error.as_deref()); + (status, finding, note) +} + +fn map_epiphany_crrc_recommendation( + loaded: bool, + state_status: ThreadEpiphanyReorientStateStatus, + pressure: &ThreadEpiphanyPressure, + decision: &ThreadEpiphanyReorientDecision, + result_status: ThreadEpiphanyReorientResultStatus, + checkpoint_present: bool, + finding_present: bool, +) -> ThreadEpiphanyCrrcRecommendation { + let build = |action: ThreadEpiphanyCrrcAction, + recommended_scene_action: Option, + reason: &str| ThreadEpiphanyCrrcRecommendation { + action, + recommended_scene_action, + reason: reason.to_string(), + }; + + if state_status == ThreadEpiphanyReorientStateStatus::Missing { + return build( + ThreadEpiphanyCrrcAction::RegatherManually, + None, + "No authoritative Epiphany state exists; re-gather source context before editing.", + ); + } + + if !checkpoint_present { + return build( + ThreadEpiphanyCrrcAction::PrepareCheckpoint, + Some(ThreadEpiphanySceneAction::Update), + "Epiphany state exists, but CRRC has no durable investigation checkpoint to resume.", + ); + } + + match result_status { + ThreadEpiphanyReorientResultStatus::Pending + | ThreadEpiphanyReorientResultStatus::Running => { + return build( + ThreadEpiphanyCrrcAction::WaitForReorientWorker, + Some(ThreadEpiphanySceneAction::ReorientResult), + "A reorientation worker is already in flight; wait or read the bound result.", + ); + } + ThreadEpiphanyReorientResultStatus::Completed => { + if finding_present { + return build( + ThreadEpiphanyCrrcAction::AcceptReorientResult, + Some(ThreadEpiphanySceneAction::ReorientAccept), + "A completed reorientation finding is available; review and explicitly accept it before continuing.", + ); + } + return build( + ThreadEpiphanyCrrcAction::ReviewReorientResult, + Some(ThreadEpiphanySceneAction::ReorientResult), + "The reorientation worker completed, but no structured finding was recorded.", + ); + } + ThreadEpiphanyReorientResultStatus::Failed + | ThreadEpiphanyReorientResultStatus::Cancelled => { + return build( + ThreadEpiphanyCrrcAction::RegatherManually, + Some(ThreadEpiphanySceneAction::ReorientResult), + "The reorientation worker ended without a usable finding; inspect the failure before relaunching.", + ); + } + ThreadEpiphanyReorientResultStatus::MissingState => { + return build( + ThreadEpiphanyCrrcAction::RegatherManually, + None, + "No authoritative Epiphany state exists; re-gather source context before editing.", + ); + } + ThreadEpiphanyReorientResultStatus::MissingBinding + | ThreadEpiphanyReorientResultStatus::BackendUnavailable + | ThreadEpiphanyReorientResultStatus::BackendMissing => {} + } + + if pressure.should_prepare_compaction + || decision.action == ThreadEpiphanyReorientAction::Regather + { + if loaded { + return build( + ThreadEpiphanyCrrcAction::LaunchReorientWorker, + Some(ThreadEpiphanySceneAction::ReorientLaunch), + "The current pressure/reorientation verdict needs a bounded worker before safe continuation.", + ); + } + return build( + ThreadEpiphanyCrrcAction::RegatherManually, + Some(ThreadEpiphanySceneAction::Reorient), + "The thread is not loaded, so CRRC can only report the regather verdict.", + ); + } + + build( + ThreadEpiphanyCrrcAction::Continue, + Some(ThreadEpiphanySceneAction::Reorient), + "Pressure is tolerable and the checkpoint remains resume-ready; continue the bounded task.", + ) +} + async fn load_completed_epiphany_reorient_finding( thread: &CodexThread, state: &EpiphanyThreadState, @@ -16093,6 +16446,120 @@ mod tests { ); } + #[test] + fn map_epiphany_crrc_recommendation_continues_clean_checkpoint() { + let pressure = map_epiphany_pressure(None); + let decision = ThreadEpiphanyReorientDecision { + action: ThreadEpiphanyReorientAction::Resume, + checkpoint_status: ThreadEpiphanyReorientCheckpointStatus::ResumeReady, + checkpoint_id: Some("ix-clean".to_string()), + pressure_level: pressure.level, + retrieval_status: ThreadEpiphanyRetrievalFreshnessStatus::Ready, + graph_status: ThreadEpiphanyGraphFreshnessStatus::Ready, + watcher_status: ThreadEpiphanyInvalidationStatus::Clean, + reasons: vec![ThreadEpiphanyReorientReason::CheckpointReady], + checkpoint_dirty_paths: Vec::new(), + checkpoint_changed_paths: Vec::new(), + active_frontier_node_ids: Vec::new(), + next_action: "Continue the bounded task.".to_string(), + note: "ready".to_string(), + }; + + let recommendation = map_epiphany_crrc_recommendation( + true, + ThreadEpiphanyReorientStateStatus::Ready, + &pressure, + &decision, + ThreadEpiphanyReorientResultStatus::MissingBinding, + true, + false, + ); + + assert_eq!(recommendation.action, ThreadEpiphanyCrrcAction::Continue); + assert_eq!( + recommendation.recommended_scene_action, + Some(ThreadEpiphanySceneAction::Reorient) + ); + } + + #[test] + fn map_epiphany_crrc_recommendation_launches_on_regather_verdict() { + let pressure = map_epiphany_pressure(None); + let decision = ThreadEpiphanyReorientDecision { + action: ThreadEpiphanyReorientAction::Regather, + checkpoint_status: ThreadEpiphanyReorientCheckpointStatus::ResumeReady, + checkpoint_id: Some("ix-drifted".to_string()), + pressure_level: pressure.level, + retrieval_status: ThreadEpiphanyRetrievalFreshnessStatus::Ready, + graph_status: ThreadEpiphanyGraphFreshnessStatus::Stale, + watcher_status: ThreadEpiphanyInvalidationStatus::Changed, + reasons: vec![ThreadEpiphanyReorientReason::CheckpointPathsChanged], + checkpoint_dirty_paths: Vec::new(), + checkpoint_changed_paths: vec![PathBuf::from("src/lib.rs")], + active_frontier_node_ids: vec!["node-a".to_string()], + next_action: "Regather.".to_string(), + note: "drifted".to_string(), + }; + + let recommendation = map_epiphany_crrc_recommendation( + true, + ThreadEpiphanyReorientStateStatus::Ready, + &pressure, + &decision, + ThreadEpiphanyReorientResultStatus::MissingBinding, + true, + false, + ); + + assert_eq!( + recommendation.action, + ThreadEpiphanyCrrcAction::LaunchReorientWorker + ); + assert_eq!( + recommendation.recommended_scene_action, + Some(ThreadEpiphanySceneAction::ReorientLaunch) + ); + } + + #[test] + fn map_epiphany_crrc_recommendation_accepts_completed_finding() { + let pressure = map_epiphany_pressure(None); + let decision = ThreadEpiphanyReorientDecision { + action: ThreadEpiphanyReorientAction::Regather, + checkpoint_status: ThreadEpiphanyReorientCheckpointStatus::RegatherRequired, + checkpoint_id: Some("ix-result".to_string()), + pressure_level: pressure.level, + retrieval_status: ThreadEpiphanyRetrievalFreshnessStatus::Ready, + graph_status: ThreadEpiphanyGraphFreshnessStatus::Ready, + watcher_status: ThreadEpiphanyInvalidationStatus::Clean, + reasons: vec![ThreadEpiphanyReorientReason::CheckpointRequestedRegather], + checkpoint_dirty_paths: Vec::new(), + checkpoint_changed_paths: Vec::new(), + active_frontier_node_ids: Vec::new(), + next_action: "Review result.".to_string(), + note: "result".to_string(), + }; + + let recommendation = map_epiphany_crrc_recommendation( + true, + ThreadEpiphanyReorientStateStatus::Ready, + &pressure, + &decision, + ThreadEpiphanyReorientResultStatus::Completed, + true, + true, + ); + + assert_eq!( + recommendation.action, + ThreadEpiphanyCrrcAction::AcceptReorientResult + ); + assert_eq!( + recommendation.recommended_scene_action, + Some(ThreadEpiphanySceneAction::ReorientAccept) + ); + } + #[test] fn reorient_finding_acceptance_builds_scratch_and_checkpoint_update() { let finding = ThreadEpiphanyReorientFinding {