Move coordinator sight to native state
This commit is contained in:
parent
fe563ee271
commit
cd7ed532dd
5 changed files with 59 additions and 168 deletions
|
|
@ -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<Value> {
|
|||
}
|
||||
|
||||
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<Value> {
|
|||
"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<Value> {
|
|||
&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<Value> {
|
|||
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<Value> {
|
|||
&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<Value> {
|
|||
&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<Value> {
|
||||
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<Value> {
|
||||
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<PathBuf> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
Ok(collect_coordinator_status(runtime_store, thread_id)?["reorientResult"].clone())
|
||||
}
|
||||
|
||||
fn maybe_apply_role_self_patch(accepted: &Value, agent_memory_dir: &Path) -> Result<Option<Value>> {
|
||||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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.
|
||||
|
|
|
|||
Reference in a new issue