From cd7ed532ddf654f381ff7afd59d1979df0c05a8e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 11 Jul 2026 20:48:19 +0100 Subject: [PATCH] Move coordinator sight to native state --- .../src/bin/epiphany-mvp-coordinator.rs | 211 ++++-------------- notes/fresh-workspace-handoff.md | 7 + notes/native-coordinator-liberation-map.md | 7 + state/ledgers.msgpack | Bin 405407 -> 405791 bytes state/map.yaml | 2 +- 5 files changed, 59 insertions(+), 168 deletions(-) diff --git a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs index 12cc8079..d98f6d61 100644 --- a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs +++ b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs @@ -54,7 +54,6 @@ const DEFAULT_APP_SERVER: &str = r"C:\Users\Meta\.cargo-target-codex\debug\codex const DEFAULT_MODEL_RUNTIME_BIN: &str = "epiphany-model-runtime"; const DEFAULT_TOOL_ADAPTER_BIN: &str = "epiphany-tool-codex-mcp-spine"; const WORKER_AUTO_TOOL_MAX_ROUNDS: usize = 24; -const REORIENT_BINDING_ID: &str = "reorient-worker"; const GRAPH_NODE_ID: &str = "reorient-target"; const WATCHED_RELATIVE_PATH: &str = "src/reorient_target.rs"; @@ -352,7 +351,7 @@ fn run_coordinator(args: &Args) -> Result { } for index in 0..args.max_steps { - let status = collect_coordinator_status(&mut client, &thread_id)?; + let status = collect_coordinator_status(&runtime_store, &thread_id)?; let mut coordinator = status .get("coordinator") .cloned() @@ -409,11 +408,7 @@ fn run_coordinator(args: &Args) -> Result { "reviewResearchResult" | "reviewModelingResult" | "reviewVerificationResult" => { let role_id = role_id_for_coordinator_action(&action) .ok_or_else(|| anyhow!("unsupported review action {action}"))?; - let result = client.send( - "thread/epiphany/roleResult", - Some(json!({"threadId": thread_id, "roleId": role_id})), - true, - )?; + let result = read_role_result(&runtime_store, &thread_id, role_id)?; push_event( &mut step, json!({"type": "roleResult", "roleId": role_id, "result": status_cli::sanitize_for_operator(result.clone())}), @@ -438,7 +433,7 @@ fn run_coordinator(args: &Args) -> Result { &mut step, json!({"type": "roleFailureReview", "roleId": role_id, "superseded": status_cli::sanitize_for_operator(superseded)}), ); - final_status = collect_coordinator_status(&mut client, &thread_id)?; + final_status = collect_coordinator_status(&runtime_store, &thread_id)?; append_operator_step_jsonl(&steps_path, &step)?; steps.push(step); continue; @@ -473,10 +468,10 @@ fn run_coordinator(args: &Args) -> Result { json!({"type": "roleAccept", "roleId": role_id, "accepted": status_cli::sanitize_for_operator(accepted)}), ); } - final_status = collect_coordinator_status(&mut client, &thread_id)?; + final_status = collect_coordinator_status(&runtime_store, &thread_id)?; } "reviewReorientResult" => { - let result = read_reorient_result(&mut client, &thread_id)?; + let result = read_reorient_result(&runtime_store, &thread_id)?; push_event( &mut step, json!({"type": "reorientResult", "result": status_cli::sanitize_for_operator(result.clone())}), @@ -547,12 +542,12 @@ fn run_coordinator(args: &Args) -> Result { &mut step, json!({"type": "workerRuntime", "roleId": role_id, "run": worker_run}), ); - let result = read_role_result(&mut client, &thread_id, role_id)?; + let result = read_role_result(&runtime_store, &thread_id, role_id)?; push_event( &mut step, json!({"type": "roleResult", "roleId": role_id, "result": status_cli::sanitize_for_operator(result.clone())}), ); - final_status = collect_coordinator_status(&mut client, &thread_id)?; + final_status = collect_coordinator_status(&runtime_store, &thread_id)?; if !matches!( result["status"].as_str(), Some("completed" | "failed" | "cancelled") @@ -601,12 +596,12 @@ fn run_coordinator(args: &Args) -> Result { &mut step, json!({"type": "workerRuntime", "roleId": "reorient-worker", "run": worker_run}), ); - let result = read_reorient_result(&mut client, &thread_id)?; + let result = read_reorient_result(&runtime_store, &thread_id)?; push_event( &mut step, json!({"type": "reorientResult", "result": status_cli::sanitize_for_operator(result.clone())}), ); - final_status = collect_coordinator_status(&mut client, &thread_id)?; + final_status = collect_coordinator_status(&runtime_store, &thread_id)?; if !matches!( result["status"].as_str(), Some("completed" | "failed" | "cancelled") @@ -775,144 +770,22 @@ fn assert_local_verse_brake_released(local_verse_store: &Path, runner_name: &str Ok(()) } -fn collect_coordinator_status( - client: &mut status_cli::AppServerClient, - thread_id: &str, -) -> Result { - let read = client.send( - "thread/read", - Some(json!({"threadId": thread_id, "includeTurns": false})), - true, - )?; - let view = client.send( - "thread/epiphany/view", - Some(json!({"threadId": thread_id, "lenses": ["scene", "pressure", "jobs", "roles", "planning", "reorient", "crrc", "coordinator"]})), - true, - )?; - let scene = json!({ - "threadId": thread_id, - "scene": view.get("scene").cloned().unwrap_or_else(|| json!(null)), - }); - let pressure = json!({ - "threadId": thread_id, - "source": "live", - "pressure": view.get("pressure").cloned().unwrap_or_else(|| json!(null)), - }); - let reorient = view.get("reorient").cloned().unwrap_or_else(|| json!(null)); - let jobs = json!({ - "threadId": thread_id, - "source": "live", - "jobs": view.get("jobs").cloned().unwrap_or_else(|| json!([])), - }); - let roles = view.get("roles").cloned().unwrap_or_else(|| json!(null)); - let planning = view.get("planning").cloned().unwrap_or_else(|| json!(null)); - let role_results = json!({ - "imagination": client.send("thread/epiphany/roleResult", Some(json!({"threadId": thread_id, "roleId": "imagination"})), true)?, - "research": client.send("thread/epiphany/roleResult", Some(json!({"threadId": thread_id, "roleId": "research"})), true)?, - "modeling": client.send("thread/epiphany/roleResult", Some(json!({"threadId": thread_id, "roleId": "modeling"})), true)?, - "verification": client.send("thread/epiphany/roleResult", Some(json!({"threadId": thread_id, "roleId": "verification"})), true)?, - }); - let reorient_result = client.send( - "thread/epiphany/reorientResult", - Some(json!({"threadId": thread_id})), - true, - )?; - let crrc = view.get("crrc").cloned().unwrap_or_else(|| json!(null)); - let coordinator = view - .get("coordinator") - .cloned() - .unwrap_or_else(|| json!(null)); - let root = env::current_dir()?; - let heartbeat_dir = root.join(".epiphany-heartbeats"); - let persona_dir = root.join(".epiphany-persona"); - let heartbeat = status_cli::native_json( - "epiphany-heartbeat-store", +fn collect_coordinator_status(runtime_store: &Path, thread_id: &str) -> Result { + let store = runtime_store.to_string_lossy(); + status_cli::native_json( + "epiphany-mvp-status", &[ - "status", - "--store", - "state/agent-heartbeats.msgpack", - "--artifact-dir", - &heartbeat_dir.to_string_lossy(), - "--limit", - "8", - ], - )?; - let latest_discord_persona = status_cli::native_json( - "epiphany-persona-discord", - &[ - "latest", - "--artifact-dir", - &persona_dir.to_string_lossy(), - "--limit", - "8", + "--native", + "--json", + "--thread-id", + thread_id, + "--thread-state-store", + &store, + "--runtime-store", + &store, ], ) - .unwrap_or_else(|_| json!({"latestArtifacts": []})); - let latest_other_persona = status_cli::native_json( - "epiphany-persona-other", - &[ - "latest", - "--artifact-dir", - &persona_dir.to_string_lossy(), - "--limit", - "8", - ], - ) - .unwrap_or_else(|_| json!({"latestArtifacts": []})); - let latest_reddit_persona = status_cli::native_json( - "epiphany-persona-reddit", - &[ - "latest", - "--artifact-dir", - &persona_dir.to_string_lossy(), - "--limit", - "8", - ], - ) - .unwrap_or_else(|_| json!({"latestArtifacts": []})); - let mut latest_artifacts = latest_discord_persona - .get("latestArtifacts") - .and_then(Value::as_array) - .cloned() - .unwrap_or_default(); - latest_artifacts.extend( - latest_reddit_persona - .get("latestArtifacts") - .and_then(Value::as_array) - .cloned() - .unwrap_or_default(), - ); - latest_artifacts.extend( - latest_other_persona - .get("latestArtifacts") - .and_then(Value::as_array) - .cloned() - .unwrap_or_default(), - ); - Ok(json!({ - "threadId": thread_id, - "read": read, - "view": view, - "scene": scene, - "pressure": pressure, - "reorient": reorient, - "jobs": jobs, - "roles": roles, - "planning": planning, - "roleResults": role_results, - "reorientResult": reorient_result, - "crrc": crrc, - "coordinator": coordinator, - "heartbeat": heartbeat, - "persona": { - "status": "ready", - "artifactDir": persona_dir, - "latestArtifacts": latest_artifacts, - "availableActions": ["PersonaBubble", "characterTurn", "discordPersonaPost", "redditPersonaPost", "otherWorldPersonaRequest"], - }, - })) } - fn resolve_model_runtime_bin(root: &Path, configured: &Path) -> Result { if configured.components().count() != 1 { return status_cli::absolute_path(configured); @@ -1329,27 +1202,12 @@ fn launch_worker_runtime_detached( })) } -fn read_role_result( - client: &mut status_cli::AppServerClient, - thread_id: &str, - role_id: &str, -) -> Result { - client.send( - "thread/epiphany/roleResult", - Some(json!({"threadId": thread_id, "roleId": role_id})), - true, - ) +fn read_role_result(runtime_store: &Path, thread_id: &str, role_id: &str) -> Result { + Ok(collect_coordinator_status(runtime_store, thread_id)?["roleResults"][role_id].clone()) } -fn read_reorient_result( - client: &mut status_cli::AppServerClient, - thread_id: &str, -) -> Result { - client.send( - "thread/epiphany/reorientResult", - Some(json!({"threadId": thread_id, "bindingId": REORIENT_BINDING_ID})), - true, - ) +fn read_reorient_result(runtime_store: &Path, thread_id: &str) -> Result { + Ok(collect_coordinator_status(runtime_store, thread_id)?["reorientResult"].clone()) } fn maybe_apply_role_self_patch(accepted: &Value, agent_memory_dir: &Path) -> Result> { @@ -1825,6 +1683,25 @@ fn home_dir() -> PathBuf { mod tests { use super::*; + #[test] + fn coordinator_status_and_result_reads_are_native() { + let source = include_str!("epiphany-mvp-coordinator.rs"); + let status_start = source.find("fn collect_coordinator_status").unwrap(); + let status_end = source.find("fn resolve_model_runtime_bin").unwrap(); + let status = &source[status_start..status_end]; + assert!(status.contains("epiphany-mvp-status")); + assert!(status.contains("--native")); + assert!(!status.contains("AppServerClient")); + assert!(!status.contains("thread/epiphany/")); + + let reads_start = source.find("fn read_role_result").unwrap(); + let reads_end = source.find("fn maybe_apply_role_self_patch").unwrap(); + let reads = &source[reads_start..reads_end]; + assert!(reads.contains("collect_coordinator_status")); + assert!(!reads.contains("AppServerClient")); + assert!(!reads.contains("thread/epiphany/")); + } + #[test] fn continue_implementation_is_not_a_passive_stop_action() { assert!(!is_stop_action("continueImplementation")); diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 9234b107..f05ccacd 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -1854,6 +1854,13 @@ publication. The bridge delegates both role and reorientation acceptance and contains zero direct witness commits; its obsolete acceptance helpers and imports are deleted. Begin `epiphany-mvp-coordinator` AppServerClient removal. +Coordinator read authority is now native. Status plus role/reorientation result +reads invoke `epiphany-mvp-status --native` over the shared CultCache store; six +Codex view/result calls were deleted and a source test rejects AppServerClient or +`thread/epiphany/*` inside those functions. Seven production mutation route +strings remain: bootstrap update/freshness, role acceptance, role/reorient +launch, and supersession update. Cut those next, then delete app-server startup. + ## Immediate Re-entry Instruction After compaction, first rehydrate and reorient from the listed files and git diff --git a/notes/native-coordinator-liberation-map.md b/notes/native-coordinator-liberation-map.md index 786f4da5..2ff37560 100644 --- a/notes/native-coordinator-liberation-map.md +++ b/notes/native-coordinator-liberation-map.md @@ -234,3 +234,10 @@ The proof must observe the stores and receipts where the invariant lives: The migration is not complete because the coordinator eventually converges on the same state after an app-server round trip. The old path must be structurally unable to decide the result. + +Coordinator sight has crossed the boundary. `collect_coordinator_status`, role +result readback, and reorientation result readback now use the native status +projection over the shared thread/runtime CultCache store. The coordinator no +longer calls Codex for `view`, `roleResult`, or `reorientResult`. Bootstrap, +launch, acceptance dispatch, and supersession mutations still use app-server +routes and remain the live cut line. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 25c2199d324b67df7be681df77c6ba3e032f87c8..923d72e693a11f8b4f272ba66e69f416146f507e 100644 GIT binary patch delta 355 zcmXw!y-EW?5XZfXxgsbiMoBDWz&0NQ8_`Pg2BxzcZb!1R*;Jr-CihA5{?tKF>eXbN%=#sq$hjsy%D7-C*Ef^pP?k(>cq+x6tC(L&O+|F;)%RLaAY z9DyK3fy5;scoSVt5Xc7>xT8FHxUzP%;fG3+4uD$lr)-3xPgdG7>IYVi41v@eAB{m4 p)d?uzHtIY`qdn_TK^m+xB5qnRiF`4?{o{@%$SY`OmCR-6{Q^yRd?o+@ delta 56 zcmbRLNMindi4BsBT*g+WmQlwT*z{^PD>D`{G9GT8%G5rUi4llFG&2yh05R+KsZ4BK Fx&ROf5e5JN diff --git a/state/map.yaml b/state/map.yaml index 079dc4df..31e74398 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -659,4 +659,4 @@ current_status: - Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass. - `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure. last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. Gjallar readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync. - next_action: Wire `epiphany-mvp-coordinator` directly to `EpiphanyCoordinatorService` typed launch/result/role-accept/reorient-accept operations and native launch context, then remove every `thread/epiphany/*` call and AppServerClient dependency. Both acceptance families now delegate fully from the bridge with zero direct witness commits. + next_action: Replace `epiphany-mvp-coordinator` mutation routes—bootstrap update/freshness, role acceptance dispatch, role/reorient launch, and failure supersession—with `EpiphanyCoordinatorService` and native launch context. Status and all result reads are already native; remove AppServerClient after the remaining seven production `thread/epiphany/*` strings are cut.