Collapse Epiphany CRRC reads into view

This commit is contained in:
Meta Vens 2026-05-12 06:17:12 +01:00
commit 32d6eed2d8
14 changed files with 117 additions and 1169 deletions

View file

@ -120,8 +120,8 @@ fn run_smoke(args: &Args) -> Result<Value> {
let notification_start = client.notification_len();
let response = client.send(
"thread/epiphany/pressure",
Some(json!({"threadId": thread_id})),
"thread/epiphany/view",
Some(json!({"threadId": thread_id, "lenses": ["pressure"]})),
true,
)?;
require(
@ -148,7 +148,7 @@ fn run_smoke(args: &Args) -> Result<Value> {
let result = json!({
"threadId": thread_id,
"codexHome": codex_home,
"source": response["source"],
"source": "thread/epiphany/view",
"status": response["pressure"]["status"],
"level": response["pressure"]["level"],
"basis": response["pressure"]["basis"],
@ -164,10 +164,6 @@ fn run_smoke(args: &Args) -> Result<Value> {
}
fn assert_unknown_pressure(response: &Value) -> Result<()> {
require(
response.get("source").and_then(Value::as_str) == Some("live"),
"fresh pressure should report live source",
)?;
let pressure = response
.get("pressure")
.ok_or_else(|| anyhow!("pressure response missing pressure object"))?;

View file

@ -129,11 +129,14 @@ fn run_smoke(args: &Args) -> Result<Value> {
.to_string();
let missing_notification_start = client.notification_len();
let missing_response = client.send(
"thread/epiphany/reorient",
Some(json!({"threadId": thread_id})),
let missing_view = client.send(
"thread/epiphany/view",
Some(json!({"threadId": thread_id, "lenses": ["reorient"]})),
true,
)?;
let missing_response = missing_view
.get("reorient")
.ok_or_else(|| anyhow!("view response missing reorient lens"))?;
assert_missing_reorient(&missing_response)?;
client.require_no_notification(
"thread/epiphany/stateUpdated",
@ -162,11 +165,14 @@ fn run_smoke(args: &Args) -> Result<Value> {
)?;
let ready_notification_start = client.notification_len();
let ready_response = client.send(
"thread/epiphany/reorient",
Some(json!({"threadId": thread_id})),
let ready_view = client.send(
"thread/epiphany/view",
Some(json!({"threadId": thread_id, "lenses": ["reorient"]})),
true,
)?;
let ready_response = ready_view
.get("reorient")
.ok_or_else(|| anyhow!("view response missing reorient lens"))?;
assert_ready_reorient(&ready_response)?;
client.require_no_notification(
"thread/epiphany/stateUpdated",
@ -368,12 +374,15 @@ fn wait_for_regather_reorient(
let mut last_response = Value::Null;
while Instant::now() < deadline {
let response = client.send(
"thread/epiphany/reorient",
Some(json!({"threadId": thread_id})),
"thread/epiphany/view",
Some(json!({"threadId": thread_id, "lenses": ["reorient"]})),
true,
)?;
if response.pointer("/decision/action").and_then(Value::as_str) == Some("regather") {
return Ok(response);
let reorient = response
.get("reorient")
.ok_or_else(|| anyhow!("view response missing reorient lens"))?;
if reorient.pointer("/decision/action").and_then(Value::as_str) == Some("regather") {
return Ok(reorient.clone());
}
last_response = response;
thread::sleep(Duration::from_millis(200));

View file

@ -30,14 +30,15 @@ The spine exists in more than twenty-five 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).
- graph traversal reflection: app-server declares experimental read-only `thread/epiphany/graphQuery`, reads the same live Epiphany state view used by context, and derives explicit node/edge lookup, path/symbol matches, bounded neighbors, edge-kind matches, and frontier neighborhoods with architecture/dataflow links plus frontier/checkpoint identity, matched selectors, and missing ids without retrieval, proposal, promotion, indexing, notification, mutation, or scheduling in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:379), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4720), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:5060), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:15201).
- planning reflection: app-server declares experimental read-only `thread/epiphany/planning`, reads the same live/stored Epiphany state view, and returns typed captures, backlog items, roadmap streams, Objective Drafts, GitHub source refs, count summaries, and the separate active objective without adopting drafts, launching work, notifying, mutating, or scheduling in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:385), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4681), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:5353), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:16295).
- 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`, derives current context pressure from `last_token_usage` rather than cumulative thread spend, treats cumulative-only telemetry as unknown, and raises `shouldPrepareCompaction` at 80% of the active auto-compact/context limit 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).
- canonical view lens reflection: app-server declares experimental read-only `thread/epiphany/view` with selectable lenses and uses it as the canonical operator read surface for pressure, reorient, CRRC, and coordinator projections. Those projections no longer have standalone protocol verbs; the separate `reorientLaunch`, `reorientResult`, and `reorientAccept` surfaces remain because they carry authority, result read-back, and review acceptance rather than mere reflection.
- context-pressure reflection: core token telemetry carries `model_auto_compact_token_limit` beside total/last usage and `model_context_window`; the `pressure` view lens reads live token usage or the latest stored rollout `TokenCount`, derives current context pressure from `last_token_usage` rather than cumulative thread spend, treats cumulative-only telemetry as unknown, and raises `shouldPrepareCompaction` at 80% of the active auto-compact/context limit 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).
- pre-compaction checkpoint intervention: app-server `TokenCount` handling now checks the same pressure mapper, and for loaded Epiphany threads crossing `shouldPrepareCompaction` it steers the active turn once with a bounded CRRC checkpoint directive to bank working context before compaction/reorientation. The per-turn dedupe bit and the pending compact handoff live in listener `ThreadState`; successful steering marks the active turn for a forced compact action at clean turn completion, while aborted turns clear the handoff. The intervention uses existing turn steering and turn-boundary automation rather than a new scheduler in [bespoke_event_handling.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/bespoke_event_handling.rs:1467), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:15405), and [thread_state.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/thread_state.rs:144).
- 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, recognizes already accepted reorientation findings so an accepted result is not recommended for acceptance again, 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 policy: the `reorient` view lens 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 [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: the `crrc` view lens reuses the same pressure/freshness/watcher/reorientation mapping plus the fixed `reorient-worker` binding and result read-back helper, recognizes already accepted reorientation findings so an accepted result is not recommended for acceptance again, 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 [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).
- role ownership projection: app-server declares experimental read-only `thread/epiphany/roles`, reuses the same state/jobs/pressure/reorient/result/CRRC signals as the coordinator loop, and projects implementation, Imagination/planning, modeling/checkpoint, verification/review, and reorientation lanes with owner roles, status, linked jobs, authority scopes, and recommended scene actions without launching, scheduling, accepting, compacting, notifying, or mutating state in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:350), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4161), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4310).
- role specialist launch/read-back/acceptance: app-server declares experimental `thread/epiphany/roleLaunch`, read-only `thread/epiphany/roleResult`, and write-gated `thread/epiphany/roleAccept`. `roleLaunch` accepts fixed Imagination/planning, modeling/checkpoint, and verification/review role ids, builds bounded specialist payloads from authoritative Epiphany state, routes them through the heartbeat-backed `epiphany_launch_job` seam, opens a typed runtime-spine job receipt, and emits the normal `jobLaunch` state update for `jobBindings` only. `roleResult` resolves heartbeat bindings through typed runtime-spine job results and seals legacy `agent_jobs` bindings, projecting structured output as a reviewable finding with verdict, summary, next safe move, inspected files, frontier/evidence ids, optional `statePatch`, optional `selfPatch`, coordinator `selfPersistence` review, raw result, and errors without mutation, promotion, scheduling, or hidden continuation. `selfPatch` is a Ghostlight-shaped lane-memory request, not project truth: the coordinator accepts only role-matched, bounded, evidence-grounded memory/goal/value/private-note mutations and refuses objectives, graphs, checkpoints, scratch, planning, jobs, authority, code edits, or raw transcripts with reasons. `roleAccept` accepts completed heartbeat findings from typed runtime-spine results, accepts completed Imagination findings only when they contain a valid planning-only patch with draft Objective Draft review gates, accepts completed modeling/checkpoint findings only with a modeling-safe `statePatch`, applies accepted project patches through the existing Epiphany state validator, appends audit observation/evidence, and carries the same `selfPersistence` review. GUI/coordinator accept paths then apply accepted `selfPatch` requests to typed CultCache role dossiers in `state/agents.msgpack` through the native `epiphany-agent-memory-store` binary. Implementation stays owned by the main coding agent, verification output remains review-only, and reorientation stays on the CRRC-specific `reorientLaunch` / `reorientResult` / `reorientAccept` path.
- shared prompt memory projection and role dossiers: the prompt config now carries `[shared].persistent_memory` in [epiphany_specialists.toml](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/prompts/epiphany_specialists.toml:2). App-server parses that block, prepends it through `epiphany_agent_prompt_with_memory`, and therefore projects the same Ghostlight-derived memory into fixed role specialist instructions, reorientation-worker instructions, coordinator notes, and CRRC checkpoint steering. The base `<epiphany_state>` doctrine reinforces the Perfect Machine rule through [prompt.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/prompt.rs:31) and [epiphany_doctrine.md](E:/Projects/EpiphanyAgent/epiphany-core/src/prompts/epiphany_doctrine.md:1). Individual Ghostlight-shaped role dossiers now live in typed CultCache `state/agents.msgpack`, and the native `epiphany-agent-memory-store` binary validates, reviews, and applies bounded self-memory patches through the Rust store.
- fixed-lane coordinator: app-server declares experimental read-only `thread/epiphany/coordinator`, composes role, pressure, reorient, CRRC, role result, and reorient result signals, and returns one current action without mutation. Review acceptance and implementation clearance are intentionally different: an accepted verification finding clears implementation only when the verifier verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings route back toward modeling/checkpoint strengthening before code work resumes. `regatherManually` is now fallback, not a supervisor-puppeteering excuse: completed unaccepted modeling findings stop at `reviewModelingResult`, modeling acceptance precedes verification, and fixed lane actions are considered before manual regather.
- fixed-lane coordinator: the `coordinator` view lens composes role, pressure, reorient, CRRC, role result, and reorient result signals, and returns one current action without mutation. Review acceptance and implementation clearance are intentionally different: an accepted verification finding clears implementation only when the verifier verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings route back toward modeling/checkpoint strengthening before code work resumes. `regatherManually` is now fallback, not a supervisor-puppeteering excuse: completed unaccepted modeling findings stop at `reviewModelingResult`, modeling acceptance precedes verification, and fixed lane actions are considered before manual regather.
- MVP operator view: native `epiphany-mvp-status` launches app-server over stdio, starts or reads one thread, gathers scene, planning, pressure, reorient, jobs, roles, Imagination/modeling/verification role results, reorient result, CRRC responses, heartbeat, and Face bubble artifacts, and renders them as text or JSON so the loop can be dogfooded without Python owning the product surface.
- MVP dogfood runner: the old Python runner was cut with the `agent_jobs` path. The replacement must be native Rust/CultCache/CultNet and complete heartbeat-owned runtime-spine job results with operator-safe artifacts.
- Agent function/API telemetry: native `epiphany-agent-telemetry` mechanically parses sealed AppServerClient JSONL transcripts into operator-safe telemetry with method counts, request shape, job/status/path fields, and visible function/tool names while sealing text, turn items, raw results, and direct agent messages.
@ -91,16 +92,18 @@ flowchart TD
QG --> Q
N --> QPLAN["thread/epiphany/planning projects captures/backlog/drafts"]
QPLAN --> Q
N --> QVIEW["thread/epiphany/view selects read lenses"]
QVIEW --> Q
A --> QP["TokenUsageInfo stores usage, context window, and auto-compact limit"]
QP --> QQ["thread/epiphany/pressure derives read-only context pressure"]
QQ --> Q
QF --> QR["thread/epiphany/reorient derives read-only resume/regather verdict"]
QP --> QQ["view.pressure derives read-only context pressure"]
QQ --> QVIEW
QF --> QR["view.reorient derives read-only resume/regather verdict"]
QQ --> QR
QR --> Q
QR --> QCRRC["thread/epiphany/crrc recommends the next explicit CRRC action"]
QR --> QVIEW
QR --> QCRRC["view.crrc recommends the next explicit CRRC action"]
QQ --> QCRRC
QJ --> QCRRC
QCRRC --> Q
QCRRC --> QVIEW
QR --> QRL["thread/epiphany/reorientLaunch can explicitly consume that verdict"]
QRL --> QRLR["thread/epiphany/reorientResult reads back worker findings"]
QRLR --> Q
@ -161,7 +164,8 @@ What this means in plain English:
- The app-server can expose a targeted read-only context shard for clients that need graph/evidence detail without pulling the full typed state.
- The app-server can expose read-only graph traversal for clients and agents that need frontier neighborhoods, path/symbol matches, or local blast-radius walks without turning traversal into proposal or promotion.
- The app-server can expose read-only planning state for clients that need captures, backlog, roadmap streams, and Objective Drafts without turning them into active objectives.
- The app-server can expose context pressure derived from real token telemetry without starting compaction or pretending vibes are telemetry.
- The app-server can expose pressure, reorient, CRRC, and coordinator projections through one canonical read-only view lens surface instead of preserving a private verb zoo.
- The pressure lens exposes context pressure derived from real token telemetry without starting compaction or pretending vibes are telemetry.
- A loaded thread can ask for a typed observation/evidence proposal without mutating state.
- A loaded thread can ask a verifier-backed promotion gate to reject or apply that proposal.
- A loaded thread can now accept explicit typed state patches and persist them immediately.
@ -1277,23 +1281,25 @@ The response is a bounded traversal result for agents that need to move around t
Graph query projection is read-only. It does not run retrieval, draft proposals, index code, promote observations, mutate `SessionState`, append rollout items, schedule work, or emit notifications. It is a map traversal, not a cartographer with a pen.
## Flow 16: Context Pressure Reflection Control Flow
## Flow 16: Context Pressure View Lens Control Flow
### Input
A client sends experimental `thread/epiphany/pressure` with:
A client sends experimental `thread/epiphany/view` with:
- `threadId`
- `lenses: ["pressure"]`
### Plain-language role
Pressure projection is the fourth Phase 6 reflection boundary. It answers "how close is this thread to context pressure?" from runtime telemetry that already drives compaction, not from transcript vibes or user panic.
Pressure projection is now a view lens. It answers "how close is this thread to context pressure?" from runtime telemetry that already drives compaction, not from transcript vibes or user panic.
The useful distinction from automatic CRRC is that this surface only reflects. It reports pressure level, measurement basis, used tokens, remaining budget, ratio, and whether preparation is advisable. It does not compact, schedule, persist state, notify `stateUpdated`, or checkpoint in-flight planning.
Protocol shape:
- `threadId`
- `lenses`
Response shape:
@ -1330,20 +1336,21 @@ Core telemetry:
2. `TurnContext` exposes the same auto-compact limit that the compaction path uses.
3. token usage updates, recomputation, and full-window marking carry the threshold into session history and `TokenCount` events.
The app-server handler:
The view handler:
1. parses the thread id.
2. checks whether the thread is loaded.
3. reads live `CodexThread::token_usage_info()` for loaded threads.
4. falls back to the latest stored rollout `TokenCount` for unloaded threads.
5. maps missing telemetry to honest `unknown`.
6. maps known telemetry against `model_auto_compact_token_limit` when present, otherwise falls back to `model_context_window`.
7. derives `low`, `elevated`, `high`, or `critical` from used tokens over the selected limit.
8. sets `shouldPrepareCompaction` only as advice when the selected ratio reaches high pressure.
2. selects the `pressure` lens.
3. checks whether the thread is loaded.
4. reads live `CodexThread::token_usage_info()` for loaded threads.
5. falls back to the latest stored rollout `TokenCount` for unloaded threads.
6. maps missing telemetry to honest `unknown`.
7. maps known telemetry against `model_auto_compact_token_limit` when present, otherwise falls back to `model_context_window`.
8. derives `low`, `elevated`, `high`, or `critical` from used tokens over the selected limit.
9. sets `shouldPrepareCompaction` only as advice when the selected ratio reaches high pressure.
### Output
The response is a read-only gauge. The Phase 6 pressure smoke proved that a fresh live thread reports `unknown`, recommends no compaction prep, emits no `thread/epiphany/stateUpdated`, and does not create Epiphany state. Focused mapper tests cover auto-compact threshold preference and context-window fallback.
The response is a read-only gauge under the view response. The Phase 6 pressure smoke proves the `thread/epiphany/view` pressure lens reports `unknown` for a fresh live thread, recommends no compaction prep, emits no `thread/epiphany/stateUpdated`, and does not create Epiphany state. Focused mapper tests cover auto-compact threshold preference and context-window fallback.
### Invariant
@ -1367,7 +1374,7 @@ turn fresh working context into decorative smoke.
The app-server event handler:
1. clones the `TokenUsageInfo` from the `TokenCount` event before forwarding the normal token-usage notification.
2. maps it through the same pressure helper used by `thread/epiphany/pressure`.
2. maps it through the same pressure helper used by the `pressure` view lens.
3. ignores unknown/low/elevated pressure and acts only when `shouldPrepareCompaction` is true.
4. requires a loaded thread with existing Epiphany state.
5. records one intervention per active turn id in listener `ThreadState`.
@ -1384,37 +1391,39 @@ findings, promote evidence, launch arbitrary specialists, or continue
implementation. It is a shove toward persistence, not a new little emperor with
a queue.
## Flow 17: Reorientation Policy Control Flow
## Flow 17: Reorientation View Lens Control Flow
### Input
A client sends experimental `thread/epiphany/reorient` with:
A client sends experimental `thread/epiphany/view` with:
- `threadId`
- `lenses: ["reorient"]`
### Plain-language role
Reorientation policy is the fifth Phase 6 reflection boundary. It answers one narrow question after wakeup or during a continuity check: does the current durable checkpoint still deserve `resume`, or has enough relevant drift accumulated that the honest answer is `regather`?
Reorientation policy is now a view lens. It answers one narrow question after wakeup or during a continuity check: does the current durable checkpoint still deserve `resume`, or has enough relevant drift accumulated that the honest answer is `regather`?
The useful distinction from automatic CRRC is that this surface only returns a verdict. It consumes the landed checkpoint, freshness, watcher, and pressure signals, but it does not compact, schedule, mutate, notify, or continue work by itself.
### Mechanism
The app-server handler:
The view handler:
1. parses the thread id.
2. reads the same live/stored thread view used by the other reflection surfaces.
3. reuses live retrieval summary and watcher snapshots for loaded threads when available.
4. reuses live token telemetry or the latest stored rollout `TokenCount`.
5. maps freshness and pressure through the existing reflection helpers.
6. evaluates one bounded verdict:
2. selects the `reorient` lens.
3. reads the same live/stored thread view used by the other reflection surfaces.
4. reuses live retrieval summary and watcher snapshots for loaded threads when available.
5. reuses live token telemetry or the latest stored rollout `TokenCount`.
6. maps freshness and pressure through the existing reflection helpers.
7. evaluates one bounded verdict:
- `regather` when state is missing, the checkpoint is missing, the checkpoint explicitly requests re-gather, checkpoint code-ref paths are dirty or newly changed, active frontier watcher hits show live drift, or an unanchored checkpoint sits on top of stale signals.
- `resume` when a `resume_ready` checkpoint remains aligned with the current signals.
7. returns typed reasons, relevant changed/dirty checkpoint paths, active frontier hits, next action text, and a human-readable note.
8. returns typed reasons, relevant changed/dirty checkpoint paths, active frontier hits, next action text, and a human-readable note.
### Output
The response is a read-only wakeup verdict. The Phase 6 reorient smoke proved that a fresh live thread regathers when there is no ember, a clean checkpoint resumes, watcher-touched checkpoint paths flip the verdict back to regather, no `thread/epiphany/stateUpdated` notification is emitted, and final thread state revision does not change.
The response is a read-only wakeup verdict under the view response. The Phase 6 reorient smoke proves the `thread/epiphany/view` reorient lens regathers when there is no ember, resumes for a clean checkpoint, flips watcher-touched checkpoint paths back to regather, emits no `thread/epiphany/stateUpdated`, and leaves final thread state revision unchanged.
### Invariant

View file

@ -178,7 +178,7 @@ Reads authoritative durable state.
Revision-gated durable state patch. No runtime lifecycle mutation except stable
links/receipts that are project truth.
### `epiphany/view`
### `thread/epiphany/view`
Reads selected lenses from durable state plus runtime telemetry. It is
read-only. The request specifies lenses such as:
@ -267,7 +267,7 @@ Delete because it compensates for bad ownership:
Collapse:
- many `thread/epiphany/*` view endpoints into `epiphany/view` lenses
- many `thread/epiphany/*` view endpoints into `thread/epiphany/view` lenses
- job binding lifecycle fields into runtime-spine jobs/results
- public findings into typed projections plus sealed forensic artifacts
@ -456,25 +456,26 @@ Commit shape:
Purpose: restore the operator-safe boundary.
### 12. Collapse View Endpoints Behind `epiphany/view`
### 12. Collapse View Endpoints Behind `thread/epiphany/view`
Add `epiphany/view` with selectable lenses. Keep old endpoints as wrappers that
call the same core view.
Add `thread/epiphany/view` with selectable lenses. Keep old endpoints as wrappers
only until their consumers have moved; wrappers are quarantine scaffolding, not
heirlooms.
Commit shape:
- no client break at first
- new view endpoint is canonical for new consumers
- old wrappers survive only while a named consumer still needs them
Purpose: stop protocol sprawl without a flag-day break.
### 13. Migrate Consumers To `epiphany/view`
### 13. Migrate Consumers To `thread/epiphany/view`
Move MVP status/Aquarium/status bridge consumers lens by lens.
Commit shape:
- old endpoint wrappers remain
- old endpoint wrappers remain only while migration is incomplete
- each consumer move is independently reversible
Purpose: reduce dependency on the exploded API.
@ -487,6 +488,11 @@ Once consumers are off wrappers, remove obsolete endpoints in small groups:
- jobs/roles
- pressure/freshness/reorient/CRRC/coordinator
Current scar: pressure, reorient, CRRC, and coordinator standalone read wrappers
are already deleted. Their live read surface is `thread/epiphany/view`; the
reorient launch/result/accept verbs remain because they are authority and review
gates, not duplicate reflection.
Purpose: actually reduce the surface area instead of preserving a compatibility
museum forever.
@ -517,7 +523,7 @@ The smallest coherent first implementation slice is:
1. add `epiphany-core/src/surfaces/`
2. move pressure derivation into core
3. leave `thread/epiphany/pressure` protocol unchanged as a wrapper
3. leave `thread/epiphany/pressure` protocol unchanged as a wrapper only for the first slice
4. move pressure policy tests into core
5. run the existing pressure smoke

View file

@ -36,7 +36,7 @@ remembers doctrine; the branch remembers the blade.
- Read `notes/epiphany-ideal-architecture-rebuild-plan.md` immediately after the teardown. It defines the smallest coherent replacement architecture: durable `EpiphanyState`, runtime-spine-owned `RuntimeState`, separate role memory/heartbeat state, `epiphany-core` as policy owner, app-server as adapter, Aquarium as reflector, typed acceptance receipts, and derived view lenses instead of a protocol verb zoo.
- Do not continue Aquarium UI, bridge, Face, or dogfood expansion until the teardown has a source-grounded cleanup slice plan. Epiphany is the foundation; patches on patches are not a purification rite, they are how the altar becomes load-bearing garbage.
- Phase 1 through Phase 5 are complete enough.
- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/roles`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/graphQuery`, `thread/epiphany/planning`, `thread/epiphany/pressure`, `thread/epiphany/reorient`, `thread/epiphany/crrc`, `thread/epiphany/coordinator`, `thread/epiphany/reorientResult`, and `thread/epiphany/roleResult`; durable `jobBindings` are now legacy launcher compatibility while new durable `runtimeLinks` hold the stable runtime-spine association. New `thread/epiphany/jobLaunch`, `thread/epiphany/jobInterrupt`, `thread/epiphany/roleLaunch`, and `thread/epiphany/reorientLaunch` writes open typed runtime-spine job receipts under `state/runtime-spine.msgpack` and do not require the Codex SQLite state runtime. Freshness carries watcher-backed invalidation inputs, graphQuery traverses authoritative typed graph neighborhoods and path/symbol matches without mutation, planning projects typed captures/backlog/roadmap/objective drafts without adopting work, roles project implementation/imagination/modeling/verification/reorientation ownership from existing signals without becoming a scheduler, `roleResult` and `reorientResult` read heartbeat-backed typed runtime-spine job results through runtime links when present, `roleAccept` and `reorientAccept` accept completed heartbeat findings by writing typed acceptance receipts while remaining explicit review gates, `thread/epiphany/crrc` recommends the next explicit CRRC action without launching, accepting, compacting, scheduling, or mutating, and `thread/epiphany/coordinator` composes those signals into a fixed-lane MVP action recommendation without becoming a writer.
- Phase 6 has read-only `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/roles`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/graphQuery`, `thread/epiphany/planning`, canonical `thread/epiphany/view` lenses, `thread/epiphany/reorientResult`, and `thread/epiphany/roleResult`; durable `jobBindings` are now legacy launcher compatibility while new durable `runtimeLinks` hold the stable runtime-spine association. The old standalone pressure, reorient, CRRC, and coordinator read verbs have been deleted; read those projections through the `pressure`, `reorient`, `crrc`, and `coordinator` view lenses. New `thread/epiphany/jobLaunch`, `thread/epiphany/jobInterrupt`, `thread/epiphany/roleLaunch`, and `thread/epiphany/reorientLaunch` writes open typed runtime-spine job receipts under `state/runtime-spine.msgpack` and do not require the Codex SQLite state runtime. Freshness carries watcher-backed invalidation inputs, graphQuery traverses authoritative typed graph neighborhoods and path/symbol matches without mutation, planning projects typed captures/backlog/roadmap/objective drafts without adopting work, roles project implementation/imagination/modeling/verification/reorientation ownership from existing signals without becoming a scheduler, `roleResult` and `reorientResult` read heartbeat-backed typed runtime-spine job results through runtime links when present, and `roleAccept` / `reorientAccept` accept completed heartbeat findings by writing typed acceptance receipts while remaining explicit review gates.
- Native `epiphany-mvp-status` is the first dogfood operator view. It starts or reads a thread through app-server and prints scene, planning, pressure, reorient, jobs, roles, Imagination/modeling/verification role result read-backs, reorient result, heartbeat, Face bubbles, and CRRC recommendation as text or machine output. The old Python status module has been cut; native Rust/CultCache/CultNet surfaces are the smoked product path.
- Native `epiphany-mvp-coordinator` is the first auditable fixed-lane coordinator runner. It starts or reads a thread through app-server, opens a native runtime-spine session, follows the harness-native coordinator action, can auto-launch modeling, verification, or reorient-worker jobs, records native runtime job/result receipts for terminal launched work, keeps semantic findings review-gated by default, and writes summary, steps, rendered snapshots, transcript, stderr, runtime-spine status, and final next-action artifacts under `.epiphany-dogfood/coordinator` or a caller-provided artifact directory. It refuses direct backend-completion mutation; full completion smoke needs live workers while execution is being cauterized into CultNet.
- Native `epiphany-runtime-spine` is the first Codex-independent runtime vertebra. It owns typed CultCache documents for runtime identity, sessions, jobs, job results, and events; opens/completes native jobs; snapshots jobs/results by runtime job id; projects job-result counts; and can emit a framed CultNet hello message advertising the native document and mutation contract surface. Codex app-server launch/read-back/acceptance is now a typed heartbeat/runtime-spine bridge with no Epiphany job-result dependency on the Codex SQLite runtime.
@ -135,7 +135,7 @@ The current spine, blessed but not yet finished:
- 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`
- read-only CRRC coordinator recommendation through the `crrc` view lens
- CRRC now recognizes already accepted reorientation findings so `reorientAccept` does not leave the operator stuck on a repeat `acceptReorientResult` recommendation.
- thin launcher metadata in durable `jobBindings`
- `thread/epiphany/jobsUpdated` remains protocol shape only for sealed legacy evacuation telemetry; app-server no longer emits Epiphany updates from old runtime `agent_job_progress` events
@ -147,10 +147,10 @@ The current spine, blessed but not yet finished:
- read-only graph traversal through `thread/epiphany/graphQuery`
- read-only planning projection through `thread/epiphany/planning`
- GUI planning adoption now belongs to Aquarium/backend API surfaces; the old EpiphanyAgent Python GUI action shim has been cut
- read-only current-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`
- read-only fixed-lane MVP action recommendation through `thread/epiphany/coordinator`
- read-only current-context pressure reflection through the `pressure` view lens
- read-only CRRC reorientation policy through the `reorient` view lens
- read-only CRRC next-action recommendation through the `crrc` view lens
- read-only fixed-lane MVP action recommendation through the `coordinator` view lens
- limited turn-complete CRRC automation for coordinator-approved compact and fixed reorient-worker launch actions
- token-count pre-compaction checkpoint intervention for loaded Epiphany turns at the `shouldPrepareCompaction` threshold, with a turn-scoped compact handoff consumed after a successful steer
- durable investigation checkpoint packet through typed state, prompt, scene, and context
@ -191,10 +191,7 @@ The exact current control flow is documented in
- `thread/epiphany/context` is read-only.
- `thread/epiphany/graphQuery` is read-only.
- `thread/epiphany/planning` is read-only.
- `thread/epiphany/pressure` is read-only.
- `thread/epiphany/reorient` is read-only.
- `thread/epiphany/crrc` is read-only.
- `thread/epiphany/coordinator` is read-only.
- `thread/epiphany/view` is read-only; its current lenses include pressure, reorient, CRRC, and coordinator projections.
- `thread/epiphany/roles` is read-only.
- `thread/epiphany/roleResult` is read-only.
- `thread/epiphany/roleAccept` is a narrow write surface for completed Imagination/planning and modeling/checkpoint `statePatch` findings only; it must not accept verification output, broad authority fields, job bindings, arbitrary implementation work, or implicit objective adoption.
@ -275,9 +272,8 @@ For the bounded reorient-guided worker launch surface, run:
```powershell
```
The same smoke now also covers the read-only `thread/epiphany/crrc`
coordinator recommendation over continue, wait, accept, interrupt, and relaunch
states.
The same smoke now also covers the read-only `crrc` view-lens coordinator
recommendation over continue, wait, accept, interrupt, and relaunch states.
For the fixed-lane MVP coordinator endpoint and runner, run:
@ -376,12 +372,14 @@ The next real move is still foundation cleanup. Start from
Phase 6 bridge/UI trail. Landed rebuild cuts: `epiphany-core/src/surfaces/`
exists, pressure view derivation and tests live in core, live acceptance writes
typed receipts keyed by runtime result id, summary-string acceptance identity
is gone for live paths, launches dual-write runtime links, and result read-back
prefers those runtime links. The next cut is to shrink/delete `jobBindings`
lifecycle/backend fields now that `runtimeLinks` can locate runtime-spine jobs,
then continue with public `raw_result` removal, `epiphany/view` lens collapse,
consumer migration, and heartbeat cognition quarantine. No Agile confetti. No
new balcony on the swaying tower.
is gone for live paths, launches dual-write runtime links, result read-back
prefers those runtime links, normal public `raw_result` is sealed, MVP consumers
use `thread/epiphany/view`, heartbeat cognition is quarantined, and the old
pressure/reorient/CRRC/coordinator read wrappers are deleted. The next cut is to
decide which remaining read verbs deserve to stay as true query surfaces and
which should collapse behind view lenses, then keep moving policy out of
app-server into `epiphany-core`. No Agile confetti. No new balcony on the
swaying tower.
The Phase 6 freshness slice is landed. It exposes read-only
`thread/epiphany/freshness` from live retrieval summaries plus graph
@ -391,10 +389,10 @@ mapped graph/frontier hits, and revision/source identity, but it does not
mutate state, schedule refresh work, or perform automatic semantic
invalidation.
The Phase 6 context-pressure slice is also landed. It exposes read-only
`thread/epiphany/pressure` from real token telemetry and the recorded
auto-compact/context limits. It does not build automatic CRRC, a scheduler, a
hidden compaction trigger, or a vibes-based gauge.
The Phase 6 context-pressure slice is also landed. It now exposes read-only
pressure through `thread/epiphany/view` from real token telemetry and the
recorded auto-compact/context limits. It does not build automatic CRRC, a
scheduler, a hidden compaction trigger, or a vibes-based gauge.
The Phase 6 investigation-checkpoint slice is also landed. It banks an
authoritative planning/source-gathering packet in typed state, validates linked
@ -418,10 +416,11 @@ action requires an explicit selected draft before writing active
objective/subgoal state plus review artifacts. Imported issues still do not
become implementation authority by themselves.
The bounded CRRC coordination/policy layer is now also landed as read-only
`thread/epiphany/reorient`. It consumes the landed pressure, freshness,
watcher, and investigation-checkpoint signals to decide whether a checkpoint
still deserves `resume` or has to admit `regather`.
The bounded CRRC coordination/policy layer is now also landed as read-only view
lenses. The `reorient` lens consumes pressure, freshness, watcher, and
investigation-checkpoint signals to decide whether a checkpoint still deserves
`resume` or has to admit `regather`; the `crrc` and `coordinator` lenses compose
that verdict into operator actions without becoming writers.
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
@ -520,8 +519,7 @@ worker, direct-thought feed, random editor launcher, or GUI-as-source-of-truth.
Live `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/roles`,
`thread/epiphany/freshness`, `thread/epiphany/context`,
`thread/epiphany/graphQuery`, `thread/epiphany/planning`, `thread/epiphany/pressure`,
`thread/epiphany/reorient`, `thread/epiphany/crrc`, `thread/epiphany/coordinator`,
`thread/epiphany/graphQuery`, `thread/epiphany/planning`, `thread/epiphany/view`,
native `epiphany-mvp-status`, native `epiphany-mvp-coordinator`, and
native `epiphany-phase6-graph-query-smoke` / native `epiphany-phase6-planning-smoke`
smokes are now guardrails, not the next organs. The architectural teardown says

Binary file not shown.

View file

@ -95,9 +95,9 @@ 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 `<epiphany_state>` 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`, bounded graph traversal through `thread/epiphany/graphQuery`, durable planning projection through `thread/epiphany/planning`, runtime pressure through `thread/epiphany/pressure`, bounded CRRC reorientation verdicts through `thread/epiphany/reorient`, read-only CRRC next-action recommendations through `thread/epiphany/crrc`, role ownership through `thread/epiphany/roles`, and reviewable role output through `thread/epiphany/roleResult`.
- 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`, bounded graph traversal through `thread/epiphany/graphQuery`, durable planning projection through `thread/epiphany/planning`, role ownership through `thread/epiphany/roles`, reviewable role output through `thread/epiphany/roleResult`, and the canonical `thread/epiphany/view` lens surface. The pressure, reorient, CRRC, and coordinator read projections now live only as view lenses, not standalone protocol verbs.
- Durable typed state writes route through `thread/epiphany/update`, accepted `thread/epiphany/promote`, `thread/epiphany/reorientAccept`, `thread/epiphany/roleAccept`, or the bounded `thread/epiphany/jobLaunch`, `thread/epiphany/jobInterrupt`, `thread/epiphany/reorientLaunch`, and `thread/epiphany/roleLaunch` authority surfaces when they mutate state or `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/graphQuery`, `thread/epiphany/planning`, `thread/epiphany/pressure`, and `thread/epiphany/reorient` stay read-only.
- `thread/epiphany/retrieve`, `thread/epiphany/distill`, `thread/epiphany/propose`, `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/freshness`, `thread/epiphany/context`, `thread/epiphany/graphQuery`, `thread/epiphany/planning`, and `thread/epiphany/view` stay read-only.
- `thread/epiphany/index` writes the retrieval catalog, not durable Epiphany understanding; catalog is auspex output, not doctrine.
- Qdrant is the preferred persistent semantic backend; BM25 remains the bootstrap/fallback/control path.
- `epiphany-core` owns prompt rendering, rollout replay helpers, retrieval/indexing, distillation, proposal, and promotion policy where possible.
@ -115,11 +115,11 @@ accepted_design:
- `thread/epiphany/freshness` is the first read-only freshness surface: it derives retrieval and graph staleness from live retrieval summaries plus frontier/churn state, and for loaded threads reflects watcher-backed invalidation inputs with changed paths plus mapped graph/frontier hits, without mutating, scheduling, or performing automatic semantic invalidation.
- `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/graphQuery` is the first read-only graph traversal surface over authoritative typed state: it returns explicit nodes, path/symbol matches, bounded neighbors, and frontier neighborhoods with architecture/dataflow links without mutating, notifying, indexing, scheduling, proposing, or promoting.
- `thread/epiphany/pressure` is the first read-only context-pressure surface: it derives pressure from current context usage, not cumulative token spend, against auto-compact/context limits without compacting, scheduling, mutating, or notifying; compaction prep starts at 80% of the active limit.
- `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.
- The `pressure` view lens derives context pressure from current context usage, not cumulative token spend, against auto-compact/context limits without compacting, scheduling, mutating, or notifying; compaction prep starts at 80% of the active limit.
- The `reorient` view lens derives a bounded read-only `resume` versus `regather` verdict from checkpoint, freshness, watcher, and pressure signals without mutating state, compacting, scheduling, or silently continuing work.
- The `crrc` view lens 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.
- A narrow auditable coordinator is central to the MVP because implementation, modeling/checkpoint, verification/review, and CRRC automation feed each other's machine-signals. CRRC itself is harness workflow automation, not a specialist-agent soul: it watches pressure/continuity, ensures persistence, triggers compaction/rehydration/reorientation when needed, and may launch a bounded reorient-worker specialist when semantic regathering is required.
- `thread/epiphany/coordinator` is the first harness-native fixed-lane coordinator surface: it composes roles, pressure, reorient, CRRC, roleResult, and reorientResult into a read-only action recommendation with target role, source signals, review requirements, automation eligibility, reason, and current lane summaries without mutating state or launching work.
- The `coordinator` view lens is the harness-native fixed-lane coordinator surface: it composes roles, pressure, reorient, CRRC, roleResult, and reorientResult into a read-only action recommendation with target role, source signals, review requirements, automation eligibility, reason, and current lane summaries without mutating state or launching work.
- Coordinator implementation clearance is stricter than review acceptance: an accepted verification/review finding only opens the implementation gate when its verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings send the loop back toward modeling/checkpoint strengthening or reorientation instead of blessing a sick machine.
- Coordinator regather handling is now fixed-lane first, not supervisor improvisation: `regatherManually` is a fallback only after reorient/modeling/verification review and launch lanes cannot advance the state, completed modeling findings must be reviewed before verification, and the CLI coordinator's explicit test-only `--auto-review` mode may accept modeling state patches before launching verification while production semantics remain review-gated.
- Native `epiphany-mvp-coordinator` is the first auditable coordinator runner: it drives the fixed single-user lanes through app-server APIs, opens a native runtime-spine session, records native runtime job/result receipts for launched specialists and reorient workers when terminal results arrive, keeps semantic findings review-gated by default, and writes coordinator summaries, JSONL steps, rendered snapshots, transcript, stderr, runtime-spine status, and final next-action artifacts. It refuses direct backend-completion mutation; full specialist completion smoke must use live workers while the execution path is cauterized into CultNet.
@ -141,7 +141,7 @@ accepted_design:
- `thread/epiphany/roles` is the first harness-native role ownership surface: it projects implementation, Imagination/planning, modeling/checkpoint, verification/review, and reorientation lanes from authoritative typed state, bound jobs, pressure/reorient/CRRC, and worker result status without launching, scheduling, accepting, compacting, or mutating state.
- `thread/epiphany/roleLaunch` and `thread/epiphany/roleResult` are the first explicit role-scoped specialist templates: Imagination/planning, modeling/checkpoint, and verification/review launch through fixed heartbeat-backed bindings with typed runtime-spine receipts and can be read back as reviewable findings from typed runtime-spine job results, while implementation stays owned by the main coding agent and reorientation stays on the CRRC-specific launch/result path.
- `thread/epiphany/roleAccept` is the first review-gated role finding acceptance path: it accepts completed heartbeat-backed findings from typed runtime-spine results, accepts Imagination/planning findings only when they return a valid planning-only `statePatch` with draft Objective Draft review gates, accepts completed modeling/checkpoint findings only with a modeling-safe `statePatch`, appends audit observation/evidence, and rejects verification, implementation, reorientation, job binding, objective adoption, or broad authority fields.
- `thread/epiphany/reorientLaunch` is the first bounded runtime consumer over the reorientation verdict: it launches one fixed `reorient-worker` heartbeat binding 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/reorientLaunch` is the first bounded runtime consumer over the reorientation verdict: it launches one fixed `reorient-worker` heartbeat binding with explicit resume-versus-regather scope, authority, and checkpoint-derived payload, without making the `reorient` view lens a writer or turning CRRC into an ambient scheduler.
- `thread/epiphany/reorientResult` is the first bounded read-back surface for that worker: heartbeat-backed bindings read typed runtime-spine job results when present and otherwise report pending.
- `thread/epiphany/reorientAccept` is the first explicit acceptance write for completed reorient-worker findings: it accepts completed heartbeat-backed findings from typed runtime-spine results, 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.
- After `thread/epiphany/reorientAccept`, CRRC recognizes the accepted reorientation finding and does not recommend accepting the same completed result again.
@ -207,7 +207,7 @@ landed_baseline:
- Phase 6 role ownership: read-only `thread/epiphany/roles` over implementation, modeling/checkpoint, verification/review, and reorientation lanes.
- Phase 6 role specialists: explicit `thread/epiphany/roleLaunch` and read-only `thread/epiphany/roleResult` templates for modeling/checkpoint and verification/review.
- Phase 6 modeling role acceptance: explicit `thread/epiphany/roleAccept` for reviewed modeling/checkpoint `statePatch` findings that can grow graphs/frontier/checkpoints through the existing state validator.
- Phase 6 coordinator: read-only `thread/epiphany/coordinator` plus native `epiphany-mvp-coordinator` and smoke coverage for the fixed-lane MVP loop.
- Phase 6 coordinator: read-only `coordinator` view lens plus native `epiphany-mvp-coordinator` and smoke coverage for the fixed-lane MVP loop.
- Phase 6 safe-boundary CRRC automation: turn-complete automation limited to coordinator-approved compact, successful pre-compaction handoff compact, and fixed reorient-worker launch actions.
- Phase 6 pre-compaction checkpoint intervention: token-count pressure hook that steers active turns once when `shouldPrepareCompaction` is reached so working context is banked before compaction, then latches a pending compact handoff for clean turn completion.
- Phase 6 Aquarium extraction: the Tauri v2 + React/WebGL operator console moved from embedded `apps/epiphany-gui` into sibling repo `E:\Projects\EpiphanyAquarium`, with its own distilled interface state, memory, doctrine, build, visual smoke, and Tauri check.
@ -273,4 +273,4 @@ rejected_paths:
current_status:
summary: Phase 1 durable state through Phase 6 coordinator/specialist/CRRC/backend organs are landed, and the May 2026 rebuild has started cutting the app-server control-plane tumor. Pressure policy now lives in `epiphany-core`; role/reorient acceptance writes typed `acceptance_receipts`; live duplicate-acceptance checks use runtime result receipt identity instead of summary strings; launch writes durable `runtime_links`; and role/reorient result read-back follows runtime links before falling back to legacy bindings.
next_action: Continue the teardown/rebuild migration from `notes/epiphany-ideal-architecture-rebuild-plan.md`. The next bounded cut is to shrink or delete `jobBindings` lifecycle/backend fields now that `runtime_links` can locate runtime-spine jobs, then remove normal public `raw_result`, add the canonical `epiphany/view` lens wrapper, migrate consumers, and quarantine heartbeat cognition blobs. Do not resume outward Aquarium/bridge/Face/dogfood expansion until this foundation cleanup is materially farther along.
next_action: Continue the teardown/rebuild migration from `notes/epiphany-ideal-architecture-rebuild-plan.md`. Landed cuts now shrink `jobBindings`, remove normal public `raw_result`, add the canonical `thread/epiphany/view` lens wrapper, migrate MVP consumers, quarantine heartbeat cognition blobs, and delete the old pressure/reorient/CRRC/coordinator read wrappers. The next bounded cut is to decide which remaining read verbs deserve to stay as true query surfaces and which should collapse behind view lenses, then continue moving policy out of app-server into `epiphany-core`. Do not resume outward Aquarium/bridge/Face/dogfood expansion until this foundation cleanup is materially farther along.

View file

@ -16435,13 +16435,6 @@
],
"type": "string"
},
"ThreadEpiphanyPressureSource": {
"enum": [
"stored",
"live"
],
"type": "string"
},
"ThreadEpiphanyPressureStatus": {
"enum": [
"unknown",

View file

@ -14322,13 +14322,6 @@
],
"type": "string"
},
"ThreadEpiphanyPressureSource": {
"enum": [
"stored",
"live"
],
"type": "string"
},
"ThreadEpiphanyPressureStatus": {
"enum": [
"unknown",

View file

@ -1,5 +0,0 @@
// 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 ThreadEpiphanyPressureSource = "stored" | "live";

View file

@ -346,7 +346,6 @@ export type { ThreadEpiphanyPlanningSummary } from "./ThreadEpiphanyPlanningSumm
export type { ThreadEpiphanyPressure } from "./ThreadEpiphanyPressure";
export type { ThreadEpiphanyPressureBasis } from "./ThreadEpiphanyPressureBasis";
export type { ThreadEpiphanyPressureLevel } from "./ThreadEpiphanyPressureLevel";
export type { ThreadEpiphanyPressureSource } from "./ThreadEpiphanyPressureSource";
export type { ThreadEpiphanyPressureStatus } from "./ThreadEpiphanyPressureStatus";
export type { ThreadEpiphanyReorientAction } from "./ThreadEpiphanyReorientAction";
export type { ThreadEpiphanyReorientCheckpointStatus } from "./ThreadEpiphanyReorientCheckpointStatus";

View file

@ -356,11 +356,6 @@ client_request_definitions! {
params: v2::ThreadEpiphanyRolesParams,
response: v2::ThreadEpiphanyRolesResponse,
},
#[experimental("thread/epiphany/coordinator")]
ThreadEpiphanyCoordinator => "thread/epiphany/coordinator" {
params: v2::ThreadEpiphanyCoordinatorParams,
response: v2::ThreadEpiphanyCoordinatorResponse,
},
#[experimental("thread/epiphany/roleLaunch")]
ThreadEpiphanyRoleLaunch => "thread/epiphany/roleLaunch" {
params: v2::ThreadEpiphanyRoleLaunchParams,
@ -396,21 +391,6 @@ client_request_definitions! {
params: v2::ThreadEpiphanyGraphQueryParams,
response: v2::ThreadEpiphanyGraphQueryResponse,
},
#[experimental("thread/epiphany/pressure")]
ThreadEpiphanyPressure => "thread/epiphany/pressure" {
params: v2::ThreadEpiphanyPressureParams,
response: v2::ThreadEpiphanyPressureResponse,
},
#[experimental("thread/epiphany/reorient")]
ThreadEpiphanyReorient => "thread/epiphany/reorient" {
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,
@ -2104,89 +2084,6 @@ mod tests {
Ok(())
}
#[test]
fn serialize_thread_epiphany_coordinator_response() -> Result<()> {
let response = ClientResponse::ThreadEpiphanyCoordinator {
request_id: RequestId::Integer(8),
response: v2::ThreadEpiphanyCoordinatorResponse {
thread_id: "thr_123".to_string(),
source: v2::ThreadEpiphanyRolesSource::Live,
state_status: v2::ThreadEpiphanyReorientStateStatus::Ready,
state_revision: Some(4),
action: v2::ThreadEpiphanyCoordinatorAction::LaunchModeling,
target_role: Some(v2::ThreadEpiphanyRoleId::Modeling),
recommended_scene_action: Some(v2::ThreadEpiphanySceneAction::RoleLaunch),
requires_review: false,
can_auto_run: true,
reason: "The modeling/checkpoint lane is ready.".to_string(),
source_signals: v2::ThreadEpiphanyCoordinatorSignals {
pressure_level: v2::ThreadEpiphanyPressureLevel::Low,
should_prepare_compaction: false,
reorient_action: v2::ThreadEpiphanyReorientAction::Resume,
crrc_action: v2::ThreadEpiphanyCrrcAction::Continue,
modeling_result_status: v2::ThreadEpiphanyRoleResultStatus::MissingBinding,
verification_result_status: v2::ThreadEpiphanyRoleResultStatus::MissingBinding,
reorient_result_status: v2::ThreadEpiphanyReorientResultStatus::MissingBinding,
},
roles: vec![v2::ThreadEpiphanyRoleLane {
id: v2::ThreadEpiphanyRoleId::Modeling,
title: "Modeling / Checkpoint".to_string(),
owner_role: "epiphany-modeler".to_string(),
status: v2::ThreadEpiphanyRoleStatus::Ready,
note: "Checkpoint ready.".to_string(),
jobs: Vec::new(),
authority_scopes: vec!["thread/epiphany/roleLaunch".to_string()],
recommended_action: Some(v2::ThreadEpiphanySceneAction::RoleLaunch),
}],
note: "Coordinator is read-only.".to_string(),
},
};
assert_eq!(response.id(), &RequestId::Integer(8));
assert_eq!(response.method(), "thread/epiphany/coordinator");
assert_eq!(
json!({
"method": "thread/epiphany/coordinator",
"id": 8,
"response": {
"threadId": "thr_123",
"source": "live",
"stateStatus": "ready",
"stateRevision": 4,
"action": "launchModeling",
"targetRole": "modeling",
"recommendedSceneAction": "roleLaunch",
"requiresReview": false,
"canAutoRun": true,
"reason": "The modeling/checkpoint lane is ready.",
"sourceSignals": {
"pressureLevel": "low",
"shouldPrepareCompaction": false,
"reorientAction": "resume",
"crrcAction": "continue",
"modelingResultStatus": "missingBinding",
"verificationResultStatus": "missingBinding",
"reorientResultStatus": "missingBinding"
},
"roles": [
{
"id": "modeling",
"title": "Modeling / Checkpoint",
"ownerRole": "epiphany-modeler",
"status": "ready",
"note": "Checkpoint ready.",
"authorityScopes": ["thread/epiphany/roleLaunch"],
"recommendedAction": "roleLaunch"
}
],
"note": "Coordinator is read-only."
}
}),
serde_json::to_value(&response)?,
);
Ok(())
}
#[test]
fn serialize_thread_epiphany_role_launch_response() -> Result<()> {
let response = ClientResponse::ThreadEpiphanyRoleLaunch {
@ -2981,235 +2878,6 @@ mod tests {
Ok(())
}
#[test]
fn serialize_thread_epiphany_pressure_response() -> Result<()> {
let response = ClientResponse::ThreadEpiphanyPressure {
request_id: RequestId::Integer(8),
response: v2::ThreadEpiphanyPressureResponse {
thread_id: "thr_123".to_string(),
source: v2::ThreadEpiphanyPressureSource::Live,
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(),
},
},
};
assert_eq!(response.id(), &RequestId::Integer(8));
assert_eq!(response.method(), "thread/epiphany/pressure");
assert_eq!(
json!({
"method": "thread/epiphany/pressure",
"id": 8,
"response": {
"threadId": "thr_123",
"source": "live",
"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."
}
}
}),
serde_json::to_value(&response)?,
);
Ok(())
}
#[test]
fn serialize_thread_epiphany_reorient_response() -> Result<()> {
let response = ClientResponse::ThreadEpiphanyReorient {
request_id: RequestId::Integer(8),
response: v2::ThreadEpiphanyReorientResponse {
thread_id: "thr_123".to_string(),
source: v2::ThreadEpiphanyReorientSource::Live,
state_status: v2::ThreadEpiphanyReorientStateStatus::Ready,
state_revision: Some(4),
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,
v2::ThreadEpiphanyReorientReason::FrontierChanged,
],
checkpoint_dirty_paths: vec![PathBuf::from("src/lib.rs")],
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(),
},
},
};
assert_eq!(response.id(), &RequestId::Integer(8));
assert_eq!(response.method(), "thread/epiphany/reorient");
assert_eq!(
json!({
"method": "thread/epiphany/reorient",
"id": 8,
"response": {
"threadId": "thr_123",
"source": "live",
"stateStatus": "ready",
"stateRevision": 4,
"decision": {
"action": "regather",
"checkpointStatus": "resumeReady",
"checkpointId": "ix-1",
"pressureLevel": "high",
"retrievalStatus": "stale",
"graphStatus": "ready",
"watcherStatus": "changed",
"reasons": [
"checkpointPathsChanged",
"frontierChanged"
],
"checkpointDirtyPaths": ["src/lib.rs"],
"checkpointChangedPaths": ["src/lib.rs"],
"activeFrontierNodeIds": ["state-spine"],
"nextAction": "Re-gather source before editing.",
"note": "Resume-ready checkpoint was invalidated by watcher/frontier drift."
}
}
}),
serde_json::to_value(&response)?,
);
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 {
@ -4719,18 +4387,6 @@ mod tests {
assert_eq!(reason, Some("thread/epiphany/roles"));
}
#[test]
fn thread_epiphany_coordinator_is_marked_experimental() {
let request = ClientRequest::ThreadEpiphanyCoordinator {
request_id: RequestId::Integer(1),
params: v2::ThreadEpiphanyCoordinatorParams {
thread_id: "thr_123".to_string(),
},
};
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request);
assert_eq!(reason, Some("thread/epiphany/coordinator"));
}
#[test]
fn thread_epiphany_role_launch_is_marked_experimental() {
let request = ClientRequest::ThreadEpiphanyRoleLaunch {
@ -4817,42 +4473,6 @@ mod tests {
assert_eq!(reason, Some("thread/epiphany/planning"));
}
#[test]
fn thread_epiphany_pressure_is_marked_experimental() {
let request = ClientRequest::ThreadEpiphanyPressure {
request_id: RequestId::Integer(1),
params: v2::ThreadEpiphanyPressureParams {
thread_id: "thr_123".to_string(),
},
};
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request);
assert_eq!(reason, Some("thread/epiphany/pressure"));
}
#[test]
fn thread_epiphany_reorient_is_marked_experimental() {
let request = ClientRequest::ThreadEpiphanyReorient {
request_id: RequestId::Integer(1),
params: v2::ThreadEpiphanyReorientParams {
thread_id: "thr_123".to_string(),
},
};
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&request);
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 {

View file

@ -4364,38 +4364,6 @@ pub enum ThreadEpiphanyRoleResultStatus {
Cancelled,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyCoordinatorParams {
pub thread_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyCoordinatorResponse {
pub thread_id: String,
pub source: ThreadEpiphanyRolesSource,
pub state_status: ThreadEpiphanyReorientStateStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub state_revision: Option<u64>,
pub action: ThreadEpiphanyCoordinatorAction,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub target_role: Option<ThreadEpiphanyRoleId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub recommended_scene_action: Option<ThreadEpiphanySceneAction>,
pub requires_review: bool,
pub can_auto_run: bool,
pub reason: String,
pub source_signals: ThreadEpiphanyCoordinatorSignals,
pub roles: Vec<ThreadEpiphanyRoleLane>,
pub note: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@ -4947,30 +4915,6 @@ pub struct ThreadEpiphanyGraphQueryMissing {
pub edge_ids: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyPressureParams {
pub thread_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyPressureResponse {
pub thread_id: String,
pub source: ThreadEpiphanyPressureSource,
pub pressure: ThreadEpiphanyPressure,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum ThreadEpiphanyPressureSource {
Stored,
Live,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@ -5025,26 +4969,6 @@ pub enum ThreadEpiphanyPressureBasis {
ModelContextWindow,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyReorientParams {
pub thread_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyReorientResponse {
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<u64>,
pub decision: ThreadEpiphanyReorientDecision,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@ -5137,39 +5061,6 @@ 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<u64>,
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<ThreadEpiphanyJob>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub reorient_finding: Option<ThreadEpiphanyReorientFinding>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub available_actions: Vec<ThreadEpiphanySceneAction>,
pub note: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]

View file

@ -155,13 +155,9 @@ use codex_app_server_protocol::ThreadEpiphanyContextResponse;
use codex_app_server_protocol::ThreadEpiphanyContextSource;
use codex_app_server_protocol::ThreadEpiphanyContextStateStatus;
use codex_app_server_protocol::ThreadEpiphanyCoordinatorAction;
use codex_app_server_protocol::ThreadEpiphanyCoordinatorParams;
use codex_app_server_protocol::ThreadEpiphanyCoordinatorResponse;
use codex_app_server_protocol::ThreadEpiphanyCoordinatorSignals;
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;
@ -197,9 +193,6 @@ use codex_app_server_protocol::ThreadEpiphanyPlanningSummary;
use codex_app_server_protocol::ThreadEpiphanyPressure;
use codex_app_server_protocol::ThreadEpiphanyPressureBasis;
use codex_app_server_protocol::ThreadEpiphanyPressureLevel;
use codex_app_server_protocol::ThreadEpiphanyPressureParams;
use codex_app_server_protocol::ThreadEpiphanyPressureResponse;
use codex_app_server_protocol::ThreadEpiphanyPressureSource;
use codex_app_server_protocol::ThreadEpiphanyPressureStatus;
use codex_app_server_protocol::ThreadEpiphanyPromoteParams;
use codex_app_server_protocol::ThreadEpiphanyPromoteResponse;
@ -213,9 +206,7 @@ use codex_app_server_protocol::ThreadEpiphanyReorientDecision;
use codex_app_server_protocol::ThreadEpiphanyReorientFinding;
use codex_app_server_protocol::ThreadEpiphanyReorientLaunchParams;
use codex_app_server_protocol::ThreadEpiphanyReorientLaunchResponse;
use codex_app_server_protocol::ThreadEpiphanyReorientParams;
use codex_app_server_protocol::ThreadEpiphanyReorientReason;
use codex_app_server_protocol::ThreadEpiphanyReorientResponse;
use codex_app_server_protocol::ThreadEpiphanyReorientResultParams;
use codex_app_server_protocol::ThreadEpiphanyReorientResultResponse;
use codex_app_server_protocol::ThreadEpiphanyReorientResultStatus;
@ -1138,10 +1129,6 @@ impl CodexMessageProcessor {
self.thread_epiphany_roles(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyCoordinator { request_id, params } => {
self.thread_epiphany_coordinator(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyRoleLaunch { request_id, params } => {
self.thread_epiphany_role_launch(to_connection_request_id(request_id), params)
.await;
@ -1170,18 +1157,6 @@ impl CodexMessageProcessor {
self.thread_epiphany_graph_query(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyPressure { request_id, params } => {
self.thread_epiphany_pressure(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyReorient { request_id, params } => {
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;
@ -4965,266 +4940,6 @@ impl CodexMessageProcessor {
self.outgoing.send_response(request_id, response).await;
}
async fn thread_epiphany_coordinator(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyCoordinatorParams,
) {
let ThreadEpiphanyCoordinatorParams { 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() {
(
ThreadEpiphanyRolesSource::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,
};
(ThreadEpiphanyRolesSource::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 runtime_store_path = if let Some(loaded_thread) = loaded_thread.as_ref() {
Some(loaded_thread.epiphany_runtime_spine_store_path().await)
} else {
None
};
let jobs = map_epiphany_jobs(thread.epiphany_state.as_ref(), retrieval_override.as_ref());
let reorient_job = jobs
.iter()
.find(|job| job.id == EPIPHANY_REORIENT_LAUNCH_BINDING_ID)
.cloned();
let (reorient_result_status, reorient_finding, _) = load_epiphany_reorient_result_snapshot(
thread.epiphany_state.as_ref(),
runtime_store_path.as_deref(),
EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
)
.await;
let checkpoint_present = thread
.epiphany_state
.as_ref()
.and_then(|state| state.investigation_checkpoint.as_ref())
.is_some();
let reorient_finding_accepted = reorient_finding.as_ref().is_some_and(|finding| {
thread
.epiphany_state
.as_ref()
.is_some_and(|state| epiphany_reorient_finding_already_accepted(state, finding))
});
let recommendation = map_epiphany_crrc_recommendation(
loaded,
state_status,
&pressure,
&decision,
reorient_result_status,
checkpoint_present,
reorient_finding.is_some(),
reorient_finding_accepted,
);
let roles = map_epiphany_roles(
thread.epiphany_state.as_ref(),
&jobs,
&decision,
&pressure,
&recommendation,
reorient_result_status,
reorient_job.as_ref(),
);
let (modeling_result_status, modeling_finding, _) =
if let Some(state) = thread.epiphany_state.as_ref() {
load_epiphany_role_result_snapshot(
state,
runtime_store_path.as_deref(),
ThreadEpiphanyRoleId::Modeling,
EPIPHANY_MODELING_ROLE_BINDING_ID,
)
.await
} else {
(
ThreadEpiphanyRoleResultStatus::MissingState,
None,
"No authoritative Epiphany state exists for this thread.".to_string(),
)
};
let modeling_result_accepted = modeling_finding.as_ref().is_some_and(|finding| {
thread
.epiphany_state
.as_ref()
.is_some_and(|state| epiphany_role_finding_already_accepted(state, finding))
});
let modeling_result_reviewable = modeling_finding
.as_ref()
.is_some_and(epiphany_modeling_finding_has_reviewable_state_patch);
let (verification_result_status, verification_finding, _) =
if let Some(state) = thread.epiphany_state.as_ref() {
load_epiphany_role_result_snapshot(
state,
runtime_store_path.as_deref(),
ThreadEpiphanyRoleId::Verification,
EPIPHANY_VERIFICATION_ROLE_BINDING_ID,
)
.await
} else {
(
ThreadEpiphanyRoleResultStatus::MissingState,
None,
"No authoritative Epiphany state exists for this thread.".to_string(),
)
};
let verification_result_accepted = verification_finding.as_ref().is_some_and(|finding| {
thread
.epiphany_state
.as_ref()
.is_some_and(|state| epiphany_role_finding_already_accepted(state, finding))
});
let verification_result_covers_current_modeling =
thread.epiphany_state.as_ref().is_none_or(|state| {
epiphany_verification_finding_covers_current_modeling(
state,
modeling_result_accepted,
modeling_finding.as_ref(),
verification_finding.as_ref(),
)
});
let modeling_result_accepted_after_verification =
thread.epiphany_state.as_ref().is_some_and(|state| {
role_finding_accepted_after(
state,
modeling_finding.as_ref(),
verification_finding.as_ref(),
)
});
let implementation_evidence_after_verification =
thread.epiphany_state.as_ref().is_some_and(|state| {
implementation_evidence_after_role_finding(state, verification_finding.as_ref())
});
let verification_result_cites_implementation_evidence =
thread.epiphany_state.as_ref().is_some_and(|state| {
epiphany_role_finding_cites_implementation_evidence(
state,
verification_finding.as_ref(),
)
});
let verification_result_allows_implementation = verification_result_accepted
&& verification_finding
.as_ref()
.is_some_and(epiphany_verification_finding_allows_implementation);
let verification_result_needs_evidence = verification_result_accepted
&& verification_finding
.as_ref()
.is_some_and(epiphany_verification_finding_needs_evidence);
let source_signals = ThreadEpiphanyCoordinatorSignals {
pressure_level: pressure.level,
should_prepare_compaction: pressure.should_prepare_compaction,
reorient_action: decision.action,
crrc_action: recommendation.action,
modeling_result_status,
verification_result_status,
reorient_result_status,
};
let coordinator = map_epiphany_coordinator(
state_status,
checkpoint_present,
&pressure,
&recommendation,
&roles,
&source_signals,
modeling_result_accepted,
modeling_result_reviewable,
modeling_result_accepted_after_verification,
implementation_evidence_after_verification,
verification_result_cites_implementation_evidence,
verification_result_covers_current_modeling,
verification_result_accepted,
verification_result_allows_implementation,
verification_result_needs_evidence,
reorient_finding_accepted,
);
let note = render_epiphany_coordinator_note(
recommendation.action,
pressure.level,
modeling_result_status,
verification_result_status,
reorient_result_status,
coordinator.action,
);
let response = ThreadEpiphanyCoordinatorResponse {
thread_id,
source,
state_status,
state_revision,
action: coordinator.action,
target_role: coordinator.target_role,
recommended_scene_action: coordinator.recommended_scene_action,
requires_review: coordinator.requires_review,
can_auto_run: coordinator.can_auto_run,
reason: coordinator.reason,
source_signals,
roles,
note,
};
self.outgoing.send_response(request_id, response).await;
}
async fn thread_epiphany_role_launch(
&self,
request_id: ConnectionRequestId,
@ -5949,282 +5664,6 @@ impl CodexMessageProcessor {
self.outgoing.send_response(request_id, response).await;
}
async fn thread_epiphany_pressure(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyPressureParams,
) {
let ThreadEpiphanyPressureParams { 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 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 (source, token_usage_info) = if let Some(loaded_thread) = loaded_thread {
(
ThreadEpiphanyPressureSource::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,
};
(ThreadEpiphanyPressureSource::Stored, token_usage_info)
};
let response = ThreadEpiphanyPressureResponse {
thread_id,
source,
pressure: map_epiphany_pressure(token_usage_info.as_ref()),
};
self.outgoing.send_response(request_id, response).await;
}
async fn thread_epiphany_reorient(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyReorientParams,
) {
let ThreadEpiphanyReorientParams { 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 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 (pressure_source, token_usage_info) = if let Some(loaded_thread) = loaded_thread {
(
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 response = ThreadEpiphanyReorientResponse {
thread_id,
source: pressure_source,
state_status,
state_revision,
decision,
};
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 runtime_store_path = if let Some(loaded_thread) = loaded_thread.as_ref() {
Some(loaded_thread.epiphany_runtime_spine_store_path().await)
} else {
None
};
let jobs = map_epiphany_jobs(thread.epiphany_state.as_ref(), retrieval_override.as_ref());
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(),
runtime_store_path.as_deref(),
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(),
finding.as_ref().is_some_and(|finding| {
thread
.epiphany_state
.as_ref()
.is_some_and(|state| epiphany_reorient_finding_already_accepted(state, finding))
}),
);
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,