Move coordinator role mutation native

This commit is contained in:
Meta Vens 2026-07-11 20:50:51 +01:00
commit b381a5f9e5
5 changed files with 138 additions and 18 deletions

View file

@ -450,10 +450,11 @@ fn run_coordinator(args: &Args) -> Result<Value> {
steps.push(step);
break;
}
let accepted = client.send(
"thread/epiphany/roleAccept",
Some(json!({"threadId": thread_id, "roleId": role_id, "expectedRevision": revision})),
true,
let accepted = accept_role(
&runtime_store,
&thread_id,
role_id,
revision.and_then(|value| u64::try_from(value).ok()),
)?;
if let Some(memory) = maybe_apply_role_self_patch(&accepted, &agent_memory_dir)? {
let mut accepted_with_memory = accepted.clone();
@ -513,7 +514,7 @@ fn run_coordinator(args: &Args) -> Result<Value> {
let role_id = role_id_for_coordinator_action(&action)
.ok_or_else(|| anyhow!("unsupported launch action {action}"))?;
let launch = launch_role(
&mut client,
&runtime_store,
&thread_id,
role_id,
revision,
@ -983,18 +984,106 @@ fn push_requested_path(paths: &mut Vec<String>, path: &str) {
}
fn launch_role(
client: &mut status_cli::AppServerClient,
runtime_store: &Path,
thread_id: &str,
role_id: &str,
expected_revision: Option<i64>,
max_runtime_seconds: u64,
) -> Result<Value> {
let mut payload =
json!({"threadId": thread_id, "roleId": role_id, "maxRuntimeSeconds": max_runtime_seconds});
if let Some(revision) = expected_revision {
payload["expectedRevision"] = json!(revision);
let service = epiphany_core::EpiphanyCoordinatorService::new(runtime_store, runtime_store);
let state = service
.state()?
.ok_or_else(|| anyhow!("cannot launch role without native coordinator state"))?;
let role = parse_role_id(role_id)?;
let expected_revision = expected_revision.and_then(|value| u64::try_from(value).ok());
let mut context = epiphany_core::render_launch_dynamic_prompt_context(
runtime_store,
&state,
epiphany_core::role_launch_context_focus(&state, epiphany_core::epiphany_role_label(role)),
)
.map_err(anyhow::Error::msg)?;
if role == epiphany_core::EpiphanyRoleResultRoleId::Verification {
context = epiphany_core::append_verification_hands_receipt_context(
context,
runtime_store,
&state,
)
.map_err(anyhow::Error::msg)?;
} else if role == epiphany_core::EpiphanyRoleResultRoleId::Modeling {
context = epiphany_core::append_modeling_work_loop_telemetry_context(
context,
runtime_store,
&state,
)
.map_err(anyhow::Error::msg)?;
}
let request = epiphany_core::build_epiphany_role_launch_request_with_dynamic_context(
thread_id,
role,
expected_revision,
Some(max_runtime_seconds),
&state,
Some(context),
)
.map_err(anyhow::Error::msg)?;
let launched = service.launch_job(
thread_id,
&state,
&request,
format!("epiphany-heartbeat-launch-{}", Uuid::new_v4()),
Uuid::new_v4().to_string(),
now(),
)?;
Ok(json!({
"bindingId": launched.binding_id,
"launcherJobId": launched.launcher_job_id,
"backendJobId": launched.backend_job_id,
"revision": launched.epiphany_state.revision,
"epiphanyState": launched.epiphany_state,
}))
}
fn accept_role(
runtime_store: &Path,
thread_id: &str,
role_id: &str,
expected_revision: Option<u64>,
) -> Result<Value> {
let service = epiphany_core::EpiphanyCoordinatorService::new(runtime_store, runtime_store);
let state = service
.state()?
.ok_or_else(|| anyhow!("cannot accept role without native coordinator state"))?;
let role = parse_role_id(role_id)?;
let accepted = service.accept_role(
thread_id,
&state,
role,
&default_binding_id_for_role(role_id),
expected_revision,
None,
now(),
&Uuid::new_v4().to_string(),
)?;
Ok(json!({
"roleId": role,
"revision": accepted.state.revision,
"epiphanyState": accepted.state,
"acceptedReceiptId": accepted.update.accepted_receipt_id,
"acceptedObservationId": accepted.update.accepted_observation_id,
"acceptedEvidenceId": accepted.update.accepted_evidence_id,
"appliedPatch": accepted.update.applied_patch,
"finding": accepted.finding,
}))
}
fn parse_role_id(role_id: &str) -> Result<epiphany_core::EpiphanyRoleResultRoleId> {
match role_id {
"imagination" => Ok(epiphany_core::EpiphanyRoleResultRoleId::Imagination),
"research" => Ok(epiphany_core::EpiphanyRoleResultRoleId::Research),
"modeling" => Ok(epiphany_core::EpiphanyRoleResultRoleId::Modeling),
"verification" => Ok(epiphany_core::EpiphanyRoleResultRoleId::Verification),
_ => Err(anyhow!("unsupported coordinator role {role_id:?}")),
}
client.send("thread/epiphany/roleLaunch", Some(payload), true)
}
fn role_id_for_coordinator_action(action: &str) -> Option<&'static str> {
@ -1702,6 +1791,28 @@ mod tests {
assert!(!reads.contains("thread/epiphany/"));
}
#[test]
fn coordinator_role_launch_and_acceptance_are_native() {
let source = include_str!("epiphany-mvp-coordinator.rs");
let start = source.find("fn launch_role").unwrap();
let end = source.find("fn role_id_for_coordinator_action").unwrap();
let native_roles = &source[start..end];
for required in [
"render_launch_dynamic_prompt_context",
"append_verification_hands_receipt_context",
"append_modeling_work_loop_telemetry_context",
".launch_job(",
".accept_role(",
] {
assert!(
native_roles.contains(required),
"missing native role seam {required:?}"
);
}
assert!(!native_roles.contains("AppServerClient"));
assert!(!native_roles.contains("thread/epiphany/"));
}
#[test]
fn continue_implementation_is_not_a_passive_stop_action() {
assert!(!is_stop_action("continueImplementation"));

View file

@ -1857,9 +1857,13 @@ 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.
`thread/epiphany/*` inside those functions. Role mutation is native too: `launch_role` loads native state, renders the same
Verse/memory context, appends Verification Hands evidence or Modeling work-loop
telemetry, builds the typed request, and calls the atomic service launch.
`accept_role` calls the service acceptance actuator directly. A source test
guards every required context seam and rejects AppServerClient/routes. Five
production routes remain: bootstrap update/freshness, supersession update, and
reorientation launch.
## Immediate Re-entry Instruction

View file

@ -238,6 +238,11 @@ 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.
longer calls Codex for `view`, `roleResult`, or `reorientResult`.
Role mutation has now crossed too. `epiphany-mvp-coordinator` builds role launch
requests from native state plus the full native Verse/memory/work-loop context,
publishes them through `EpiphanyCoordinatorService::launch_job`, and dispatches
auto-review through `accept_role`. The role launch and role acceptance routes
are gone. Bootstrap/freshness, supersession update, and reorientation launch are
the five remaining production route strings.

Binary file not shown.

View file

@ -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: 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.
next_action: Replace the five remaining `epiphany-mvp-coordinator` mutation routes: bootstrap update/freshness, failure supersession update, and reorientation launch. Status/results plus role launch/acceptance are native and preserve full Verse/memory/work-loop context. Remove AppServerClient startup after these final routes are cut.