diff --git a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs index 156bbd10..4e5d1183 100644 --- a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs +++ b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs @@ -29,6 +29,7 @@ mod status_cli; const DEFAULT_APP_SERVER: &str = r"C:\Users\Meta\.cargo-target-codex\debug\codex-app-server.exe"; const DEFAULT_MODEL_RUNTIME_BIN: &str = "epiphany-model-runtime"; +const DEFAULT_TOOL_ADAPTER_BIN: &str = "epiphany-tool-codex-mcp-spine"; const REORIENT_BINDING_ID: &str = "reorient-worker"; const GRAPH_NODE_ID: &str = "reorient-target"; const WATCHED_RELATIVE_PATH: &str = "src/reorient_target.rs"; @@ -44,6 +45,7 @@ fn main() -> Result<()> { struct Args { app_server: PathBuf, model_runtime_bin: PathBuf, + tool_adapter_bin: PathBuf, model_provider: String, thread_id: Option, cwd: PathBuf, @@ -58,6 +60,7 @@ struct Args { max_runtime_seconds: u64, ephemeral: bool, auto_review: bool, + auto_tools: bool, bootstrap_smoke_state: bool, simulate_high_pressure: bool, simulate_source_drift: bool, @@ -71,6 +74,7 @@ impl Args { let mut parsed = Args { app_server: PathBuf::from(DEFAULT_APP_SERVER), model_runtime_bin: PathBuf::from(DEFAULT_MODEL_RUNTIME_BIN), + tool_adapter_bin: PathBuf::from(DEFAULT_TOOL_ADAPTER_BIN), model_provider: "openai-codex".to_string(), thread_id: None, cwd: root.clone(), @@ -87,6 +91,7 @@ impl Args { max_runtime_seconds: 180, ephemeral: true, auto_review: false, + auto_tools: true, bootstrap_smoke_state: false, simulate_high_pressure: false, simulate_source_drift: false, @@ -98,6 +103,9 @@ impl Args { "--model-runtime-bin" => { parsed.model_runtime_bin = take_path(&mut args, "--model-runtime-bin")?; } + "--tool-adapter-bin" => { + parsed.tool_adapter_bin = take_path(&mut args, "--tool-adapter-bin")?; + } "--openai-runtime-bin" => { parsed.model_runtime_bin = take_path(&mut args, "--openai-runtime-bin")?; } @@ -132,6 +140,8 @@ impl Args { "--ephemeral" => parsed.ephemeral = true, "--no-ephemeral" => parsed.ephemeral = false, "--auto-review" => parsed.auto_review = true, + "--auto-tools" => parsed.auto_tools = true, + "--no-auto-tools" => parsed.auto_tools = false, "--test-complete-backend" => { return Err(anyhow!( "--test-complete-backend was removed: native coordinator refuses direct private state-store job mutation; use live workers or a future CultNet job-result API" @@ -153,6 +163,7 @@ fn run_coordinator(args: &Args) -> Result { let app_server = status_cli::absolute_path(&args.app_server)?; let mut cwd = status_cli::absolute_path(&args.cwd)?; let model_runtime_bin = resolve_model_runtime_bin(&root, &args.model_runtime_bin)?; + let tool_adapter_bin = resolve_model_runtime_bin(&root, &args.tool_adapter_bin)?; let codex_home = status_cli::absolute_path(&args.codex_home)?; let artifact_dir = status_cli::absolute_path(&args.artifact_dir)?; let agent_memory_dir = status_cli::absolute_path(&args.agent_memory_dir)?; @@ -386,14 +397,17 @@ fn run_coordinator(args: &Args) -> Result { ); let worker_run = run_worker_runtime( &model_runtime_bin, + &tool_adapter_bin, &args.model_provider, &runtime_store, &codex_home, + &cwd, &worker_job_id, role_id, index, &artifact_dir, args.timeout_seconds, + args.auto_tools, )?; push_event( &mut step, @@ -425,14 +439,17 @@ fn run_coordinator(args: &Args) -> Result { ); let worker_run = run_worker_runtime( &model_runtime_bin, + &tool_adapter_bin, &args.model_provider, &runtime_store, &codex_home, + &cwd, &worker_job_id, "reorient-worker", index, &artifact_dir, args.timeout_seconds, + args.auto_tools, )?; push_event( &mut step, @@ -521,6 +538,11 @@ fn run_coordinator(args: &Args) -> Result { "modelRuntimeBin".to_string(), model_runtime_bin.display().to_string(), ), + ( + "toolAdapterBin".to_string(), + tool_adapter_bin.display().to_string(), + ), + ("autoTools".to_string(), args.auto_tools.to_string()), ]), }; put_coordinator_run_receipt(&runtime_store, &coordinator_run_receipt)?; @@ -528,6 +550,8 @@ fn run_coordinator(args: &Args) -> Result { "objective": "Coordinate the Epiphany MVP lanes over existing app-server APIs.", "artifactDir": artifact_dir, "modelRuntimeBin": model_runtime_bin, + "toolAdapterBin": tool_adapter_bin, + "autoTools": args.auto_tools, "modelProvider": args.model_provider, "codexHome": codex_home, "runtimeStore": runtime_store, @@ -676,14 +700,20 @@ fn resolve_model_runtime_bin(root: &Path, configured: &Path) -> Result return status_cli::absolute_path(configured); } let exe_name = format!("{}.exe", configured.display()); + let cargo_target_candidate = env::var_os("CARGO_TARGET_DIR") + .map(PathBuf::from) + .map(|path| path.join("debug").join(&exe_name)); let candidates = [ - root.join("epiphany-openai-runtime") - .join("target") - .join("debug") - .join(&exe_name), - root.join("target").join("debug").join(&exe_name), + cargo_target_candidate, + Some( + root.join("epiphany-openai-runtime") + .join("target") + .join("debug") + .join(&exe_name), + ), + Some(root.join("target").join("debug").join(&exe_name)), ]; - for candidate in candidates { + for candidate in candidates.into_iter().flatten() { if candidate.exists() { return Ok(candidate); } @@ -736,14 +766,17 @@ fn worker_job_id_from_launch(launch: &Value) -> Result { fn run_worker_runtime( model_runtime_bin: &Path, + tool_adapter_bin: &Path, model_provider: &str, runtime_store: &Path, codex_home: &Path, + cwd: &Path, job_id: &str, role_id: &str, step_index: usize, artifact_dir: &Path, timeout_seconds: u64, + auto_tools: bool, ) -> Result { let stdout_path = artifact_dir.join(format!( "step-{step_index:02}-{}-worker-runtime.stdout.json", @@ -763,9 +796,16 @@ fn run_worker_runtime( .arg("--codex-home") .arg(codex_home) .arg("--job-id") - .arg(job_id) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + .arg(job_id); + if auto_tools { + command + .arg("--auto-tools") + .arg("--tool-adapter-bin") + .arg(tool_adapter_bin) + .arg("--cwd") + .arg(cwd); + } + command.stdout(Stdio::piped()).stderr(Stdio::piped()); let mut child = command .spawn() .with_context(|| format!("failed to spawn {}", model_runtime_bin.display()))?; diff --git a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs index c4bb3a92..361e5f5c 100644 --- a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs +++ b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs @@ -1,6 +1,7 @@ use std::env; use std::fs; use std::path::PathBuf; +use std::process::Command; use anyhow::Context; use anyhow::Result; @@ -16,12 +17,16 @@ use epiphany_openai_runtime::EpiphanyWorkerRuntimeOptions; use epiphany_openai_runtime::OPENAI_RUNTIME_ROLE; use epiphany_openai_runtime::assistant_text_from_model_events; use epiphany_openai_runtime::build_tool_followup_model_request; +use epiphany_openai_runtime::build_worker_model_request; +use epiphany_openai_runtime::complete_worker_job_from_assistant_text; use epiphany_openai_runtime::default_codex_home; use epiphany_openai_runtime::default_options; use epiphany_openai_runtime::ensure_openai_runtime_ready; +use epiphany_openai_runtime::load_worker_launch_request; use epiphany_openai_runtime::record_openai_events; use epiphany_openai_runtime::run_model_turn; use epiphany_openai_runtime::run_openai_model_turn; +use epiphany_openai_runtime::run_tool_followup_model_turn; use epiphany_openai_runtime::run_worker_launch; use serde_json::json; use uuid::Uuid; @@ -68,14 +73,20 @@ async fn main() -> Result<()> { "run-worker" => { let options = parse_run_worker_options(args.collect())?; require_supported_provider(&options.provider)?; - let summary = run_worker_launch(EpiphanyWorkerRuntimeOptions { - store_path: options.store_path, - codex_home: options.codex_home, - provider: options.provider, - job_id: options.job_id, - model: options.model, - }) - .await?; + let summary = if options.auto_tools { + run_worker_launch_with_tool_continuation(options).await? + } else { + serde_json::to_value( + run_worker_launch(EpiphanyWorkerRuntimeOptions { + store_path: options.store_path, + codex_home: options.codex_home, + provider: options.provider, + job_id: options.job_id, + model: options.model, + }) + .await?, + )? + }; print_json(&summary)?; } "tool-followup" => { @@ -260,6 +271,10 @@ struct RunWorkerCliOptions { codex_home: PathBuf, job_id: String, model: String, + auto_tools: bool, + tool_adapter_bin: Option, + cwd: Option, + max_tool_rounds: usize, } struct ToolFollowupCliOptions { @@ -354,6 +369,10 @@ fn parse_run_worker_options(args: Vec) -> Result { let mut codex_home = default_codex_home()?; let mut job_id = None; let mut model = "gpt-5.4".to_string(); + let mut auto_tools = false; + let mut tool_adapter_bin = None; + let mut cwd = None; + let mut max_tool_rounds = 4usize; let mut iter = args.into_iter(); while let Some(arg) = iter.next() { match arg.as_str() { @@ -362,6 +381,14 @@ fn parse_run_worker_options(args: Vec) -> Result { "--codex-home" => codex_home = PathBuf::from(next_value(&mut iter, "--codex-home")?), "--job-id" => job_id = Some(next_value(&mut iter, "--job-id")?), "--model" | "--default-model" => model = next_value(&mut iter, "--model")?, + "--auto-tools" => auto_tools = true, + "--tool-adapter-bin" => { + tool_adapter_bin = Some(PathBuf::from(next_value(&mut iter, "--tool-adapter-bin")?)) + } + "--cwd" => cwd = Some(PathBuf::from(next_value(&mut iter, "--cwd")?)), + "--max-tool-rounds" => { + max_tool_rounds = next_value(&mut iter, "--max-tool-rounds")?.parse()? + } other => return Err(anyhow!("unknown run-worker argument: {other}")), } } @@ -371,6 +398,10 @@ fn parse_run_worker_options(args: Vec) -> Result { codex_home, job_id: job_id.context("run-worker requires --job-id")?, model, + auto_tools, + tool_adapter_bin, + cwd, + max_tool_rounds, }) } @@ -400,6 +431,130 @@ fn parse_tool_followup_options(args: Vec) -> Result Result { + let tool_adapter_bin = options + .tool_adapter_bin + .clone() + .context("run-worker --auto-tools requires --tool-adapter-bin")?; + let launch_request = load_worker_launch_request(&options.store_path, &options.job_id)?; + let initial_request = + build_worker_model_request(&launch_request, &options.provider, &options.model)?; + let openai_options = EpiphanyOpenAiRuntimeOptions { + store_path: options.store_path.clone(), + codex_home: options.codex_home.clone(), + session_id: format!("openai-worker-session-{}", launch_request.binding_id), + job_id: format!("openai-worker-{}", launch_request.job_id), + objective: format!( + "Run Epiphany worker {} for {}", + launch_request.job_id, launch_request.binding_id + ), + coordinator_note: "Native worker runtime route; Codex is auth/model transport only." + .to_string(), + default_model: Some(options.model.clone()), + }; + let mut current_request_id = initial_request.request_id.clone(); + let mut current_options = openai_options.clone(); + let mut openai_summary = + run_model_turn(&options.provider, current_options.clone(), initial_request).await?; + let mut tool_rounds = Vec::new(); + + for round in 0..options.max_tool_rounds { + if openai_summary.tool_intent_ids.is_empty() { + break; + } + let mut adapter_runs = Vec::new(); + for intent_id in openai_summary.tool_intent_ids.clone() { + adapter_runs.push(run_tool_adapter( + &tool_adapter_bin, + &options.store_path, + &options.codex_home, + options.cwd.as_ref(), + &intent_id, + )?); + } + let followup_request_id = format!("{}-tool-followup-{round}", current_request_id); + current_options.job_id = format!("{}-tool-followup-{round}", openai_options.job_id); + openai_summary = run_tool_followup_model_turn( + &options.provider, + current_options.clone(), + ¤t_request_id, + &followup_request_id, + ) + .await?; + current_request_id = followup_request_id; + tool_rounds.push(json!({ + "round": round, + "adapterRuns": adapter_runs, + "followupRequestId": current_request_id, + "summary": openai_summary, + })); + } + + if !openai_summary.tool_intent_ids.is_empty() { + return Err(anyhow!( + "worker {} still requested tools after {} automatic tool rounds", + launch_request.job_id, + options.max_tool_rounds + )); + } + + let assistant_text = + assistant_text_from_model_events(&options.store_path, ¤t_request_id)?; + let worker_result = complete_worker_job_from_assistant_text( + &options.store_path, + &launch_request, + ¤t_request_id, + &openai_summary, + &assistant_text, + )?; + + Ok(json!({ + "store": options.store_path, + "jobId": launch_request.job_id, + "bindingId": launch_request.binding_id, + "role": launch_request.role, + "requestId": current_request_id, + "openaiResultId": openai_summary.result_id, + "workerResultId": worker_result.result_id, + "verdict": worker_result.verdict, + "toolRounds": tool_rounds, + })) +} + +fn run_tool_adapter( + tool_adapter_bin: &PathBuf, + store_path: &PathBuf, + codex_home: &PathBuf, + cwd: Option<&PathBuf>, + intent_id: &str, +) -> Result { + let mut command = Command::new(tool_adapter_bin); + command + .arg("run") + .arg("--store") + .arg(store_path) + .arg("--intent-id") + .arg(intent_id) + .arg("--codex-home") + .arg(codex_home); + if let Some(cwd) = cwd { + command.arg("--cwd").arg(cwd); + } + let output = command + .output() + .with_context(|| format!("failed to spawn {}", tool_adapter_bin.display()))?; + if !output.status.success() { + return Err(anyhow!( + "tool adapter failed for {intent_id}: {}{}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout) + )); + } + serde_json::from_slice(&output.stdout).context("tool adapter returned invalid JSON") +} + fn parse_tool_followup_turn_options(args: Vec) -> Result { let mut provider = DEFAULT_PROVIDER.to_string(); let mut store_path = PathBuf::from(DEFAULT_STORE); @@ -496,5 +651,5 @@ fn now() -> String { } fn usage() -> &'static str { - "usage: epiphany-model-runtime [--provider openai-codex] [--store path] [--codex-home path] [--request path] [--request-id id] [--followup-request-id id] [--output path] [--session-id id] [--job-id id] [--objective text] [--default-model model] [--output-last-message path]" + "usage: epiphany-model-runtime [--provider openai-codex] [--store path] [--codex-home path] [--request path] [--request-id id] [--followup-request-id id] [--output path] [--session-id id] [--job-id id] [--objective text] [--default-model model] [--output-last-message path] [--auto-tools --tool-adapter-bin path --cwd path --max-tool-rounds n]" } diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 3912e18c..7eef4c9a 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -40,7 +40,7 @@ remembers doctrine; the branch remembers the blade. - `epiphany-model-runtime` is the current provider-neutral executable boundary. It is built by the `epiphany-openai-runtime` crate for now and accepts `--provider openai-codex`; the old `epiphany-openai-runtime` binary, `--openai-runtime-bin`, and `openai-runtime` executor name survive only as compatibility aliases. Coordinator run mode, local wrapper run mode, and repo birth runner should call `--model-runtime-bin` plus `--model-provider`. The typed request/event/receipt contract is now provider-neutral in `epiphany-model-adapter` as `epiphany.model_request.v0`, `epiphany.model_stream_event.v0`, `epiphany.model_receipt.v0`, and `epiphany.model_adapter_status.v0`; OpenAI/Codex documents are adapter evidence behind that boundary, not scheduler/status/state authority. - Coordinator plan/run passes now emit `epiphany.coordinator_run_receipt.v0` into runtime-spine. `coordinator-summary.json` and `coordinator-steps.jsonl` remain useful operator artifacts, but the durable receipt of what the coordinator decided is typed CultCache/CultNet state. - The first tool/MCP contract surface exists in `epiphany-tool-adapter`: `epiphany.tool_capability.v0`, `epiphany.tool_invocation_intent.v0`, and `epiphany.tool_invocation_receipt.v0`. Runtime-spine advertises these as the Epiphany-facing tool boundary. `epiphany-model-runtime` now materializes complete MCP-shaped model tool calls as typed invocation intents, and `epiphany-tool-codex-mcp-spine` is the quarantined executing adapter: it consumes one typed invocation intent from the mixed runtime-spine CultCache store, calls user-declared Codex MCP through `codex-mcp`, and emits a typed invocation receipt. Raw MCP JSON remains protocol-edge cargo; Codex MCP is transport scaffolding, not Epiphany state, prompt, scheduler, or policy authority. -- Native status now exposes that quarantined bridge spine without granting it authority. `runtime_spine_status` counts tool invocation intents, receipts, and pending calls; `runtime_tool_invocation_statuses` derives read-only intent/receipt status rows; `epiphany-runtime-spine status` reports the counts; and `epiphany-mvp-status --runtime-store ` renders the latest tool calls under `tools`. This is operator visibility only. The model-runtime feedback seam is also now typed: MCP-shaped model tool calls preserve the original provider `call_id` plus source model request id; `epiphany-model-runtime tool-followup` builds a provider-neutral follow-up `EpiphanyModelRequest` from completed typed tool receipts using the previous provider response id; and `epiphany-model-runtime tool-followup-turn` derives and runs that continuation through the selected provider in one runtime-owned operation. Codex still executes the quarantined MCP edge; Epiphany owns the continuation request shape and invocation boundary. +- Native status now exposes that quarantined bridge spine without granting it authority. `runtime_spine_status` counts tool invocation intents, receipts, and pending calls; `runtime_tool_invocation_statuses` derives read-only intent/receipt status rows; `epiphany-runtime-spine status` reports the counts; and `epiphany-mvp-status --runtime-store ` renders the latest tool calls under `tools`. This is operator visibility only. The model-runtime feedback seam is also now typed: MCP-shaped model tool calls preserve the original provider `call_id` plus source model request id; `epiphany-model-runtime tool-followup` builds a provider-neutral follow-up `EpiphanyModelRequest` from completed typed tool receipts using the previous provider response id; and `epiphany-model-runtime tool-followup-turn` derives and runs that continuation through the selected provider in one runtime-owned operation. `epiphany-model-runtime run-worker --auto-tools --tool-adapter-bin ` now lets a launched worker automatically execute typed MCP intents through the quarantined Codex MCP adapter, feed receipts back through follow-up model turns, and only then parse/complete the worker result. Codex still executes the quarantined MCP edge; Epiphany owns the continuation request shape and invocation boundary. - The active foundation directive is now `notes/codex-starvation-and-cultnet-liberation-plan.md`. The previous Codex app-server control-plane rebuild made Epiphany less rotten inside Codex, but that is not the destination. Epiphany must become a native CultCache/CultMesh/CultNet runtime while remaining an honest modified Codex-derived backend for subscription auth/model use. Codex should remain relatively vanilla and keep doing Codex things, including useful app-server and streaming bridge affordances, but it must not own Epiphany state, processes, prompts, scheduler decisions, or policy. The bridge handles interop; Codex and Epiphany do not get to rummage around in each other's organs. - `notes/epiphany-cultmesh-dreaming-roadmap.md` is the new concrete design for distributed Epiphany dreaming. The invariant is hard: private state stays local; public dreams are separately authored typed documents distributed through CultMesh/CultNet; foreign dreams are thought weather until a reviewed local adoption receipt digests them. The first code slice should be dream schemas plus a local CultMesh-backed store, not public fanout. - CultMesh is now the preferred Rust abstraction for that local store work. The compile-time Rust substrate is repo-contained: `vendor/cultcache-rs`, `vendor/cultnet-rs`, and `vendor/cultmesh-rs`. The old `E:\Projects\CultLib\crates\*` dependency body is dead in this checkout and must not be revived by accident. Vendored CultCache reads/writes `cultcache.store.v1`, so `state/ledgers.msgpack` and `epiphany-state status` are aligned again. The first integrations are deliberately small: status, Verse policy, global room policy, organ contract policy, native operator status, native operator snapshots, and local operator run intent/receipt round-trip through CultMesh. Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-status -- smoke --store .\.epiphany-smoke\cultmesh\operator-status.ccmp --runtime-id epiphany-cultmesh-status-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-snapshot -- smoke --store .\.epiphany-smoke\cultmesh\operator-snapshots.ccmp --runtime-id epiphany-operator-snapshot-smoke --snapshot-id smoke-status`, and `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-run -- smoke --store .\.epiphany-smoke\cultmesh\operator-runs.ccmp --runtime-id epiphany-operator-run-smoke --run-id smoke-run` for the focused proofs. Future local dream/status/Verse work should start from CultMesh, not raw CultNet, unless the task is polishing the CultMesh internals themselves. Preserve the three Verse tiers: `epiphany-internal` for private sub-agent typed state, `gamecult-local` for trusted LAN/Yggdrasil-tunnel GameCult sharing, and `epiphany-global` for untrusted public dreams plus topic-specific threaded public rooms for Face posts. @@ -183,7 +183,7 @@ remembers doctrine; the branch remembers the blade. - The planning loop is now runtime-backed and operator-visible. Typed captures, backlog items, roadmap streams, Objective Drafts, and GitHub issue source refs live in `EpiphanyThreadState`, validate through revision-gated `thread/epiphany/update`, render into prompts when present, project read-only through the `planning` view lens, render in the GUI Planning panel, can be synthesized by the fixed Imagination/planning lane, and can be explicitly adopted through the artifacted `adoptObjectiveDraft` GUI action. Chat is deliberation, not an objective pipe; ideas and GitHub Issues remain planning state until explicit human adoption. - The bridge/dashboard slice is now landed far enough to test locally. Native `epiphany-rider-bridge` inspects Rider installation, solution, VCS, changed files, and captures file/selection/symbol context into `.epiphany-gui/rider` artifacts; Aquarium has Inspect Rider and renders Rider audit artifacts in Environment; Aquarium also embeds the adjacent EpiphanyGraph viewer over typed `graphs.architecture`, `graphs.dataflow`, and `graphs.links`. - A first Rider plugin scaffold lives under `integrations/rider`: tool window, Refresh status, and Send Context to Epiphany action. It shells through native `epiphany-rider-bridge` and does not own state. `gradle` is not installed on this machine, so the scaffold is not build-verified yet. -- Next real move: continue Codex starvation and native Epiphany ownership by letting the coordinator choose and invoke the `tool-followup-turn` primitive when pending tool receipts make continuation appropriate. Quarantine Rider/Unity bridge work for now; those artifacts remain optional later environment evidence, not MVP blockers. Keep EpiphanyAgent focused on backend contracts, typed state, coordinator policy, heartbeat scheduling, Face guardrails, prompt-authority separation, and the ongoing purification from Python/JSON scaffolding into Rust/CultCache/CultMesh/CultNet organs. Resume repo birth/Aquarium/Rider/Unity work only when the user explicitly shifts priority back outward. GitHub Issues import is deferred until the backlog source is fresh enough to deserve Imagination's attention. +- Next real move: continue Codex starvation and native Epiphany ownership by exercising the auto-tool worker loop in a live coordinator run, then harden whichever receipt/result edge fails first. Quarantine Rider/Unity bridge work for now; those artifacts remain optional later environment evidence, not MVP blockers. Keep EpiphanyAgent focused on backend contracts, typed state, coordinator policy, heartbeat scheduling, Face guardrails, prompt-authority separation, and the ongoing purification from Python/JSON scaffolding into Rust/CultCache/CultMesh/CultNet organs. Resume repo birth/Aquarium/Rider/Unity work only when the user explicitly shifts priority back outward. GitHub Issues import is deferred until the backlog source is fresh enough to deserve Imagination's attention. - The repo is an Epiphany fork of Codex, not a Codex preset. - `vendor/codex` is tracked directly, not a submodule. - `epiphany-core` owns the heavy Epiphany organs where practical. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 671ed9c8..454a5860 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index b616594d..66ad882e 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -72,7 +72,7 @@ active_subgoals: - Use `notes/epiphany-cultmesh-dreaming-roadmap.md` as the design authority for distributed Epiphany dreaming. Public dreams are separately authored typed documents distributed through CultMesh/CultNet; private state remains local and cannot be shared by flipping a visibility flag. Foreign dreams are external thought weather until a reviewed local adoption receipt digests them into memory, planning, doctrine, or governance. - CultMesh is now the preferred ergonomic Rust abstraction over CultCache and CultNet for Epiphany-owned local database surfaces. The Rust substrate is repo-contained under `vendor/cultcache-rs`, `vendor/cultnet-rs`, and `vendor/cultmesh-rs`; the old `E:\Projects\CultLib\crates\*` path is dead in this checkout and must not be reintroduced as a compile-time dependency. Future local Verse/dream/status work should start at CultMesh unless it is explicitly polishing CultMesh internals. Preserve the three-Verse boundary: `epiphany-internal` for private sub-agent typed state, `gamecult-local` for trusted LAN/Yggdrasil-tunnel GameCult project sharing, and `epiphany-global` for untrusted public dreams plus topic-specific threaded public rooms for Face posts. - The active priority is Codex starvation and CultNet liberation. Use `notes/codex-starvation-and-cultnet-liberation-plan.md` as the directive and `notes/codex-auth-spine-inventory.md` as the source-grounded keeper list for the retained OpenAI subscription auth/model transport bridge. Classify all Epiphany-relevant JSON contamination, define the native CultNet runtime boundary, keep worker launch/result/selfPatch cargo as typed documents, evacuate Epiphany operator/state/process authority from Codex JSON-RPC, wrap MCP's unavoidable JSON protocol edge in typed Epiphany intent/result/receipt documents, and keep cutting until Epiphany can run its own state and processes without `codex_message_processor.rs` except for bridge compatibility. - - The next Codex-starvation slice is coordinator policy for typed tool-result follow-up turns. The quarantined Codex MCP spine may execute typed tool intents and emit typed receipts, and the model runtime can now build and run a provider-neutral follow-up turn from those receipts, but the coordinator still needs to own when that primitive is invoked. Codex transports tool calls; it must not inject prompt doctrine or decide what Epiphany agents do with tool results. + - The next Codex-starvation slice is live exercise and hardening of automatic tool-result continuation in worker runtime. The quarantined Codex MCP spine may execute typed tool intents and emit typed receipts; the model runtime can build and run provider-neutral follow-up turns from those receipts; and coordinator run mode now passes the quarantined tool adapter into worker runtime with auto-tools enabled by default. Codex transports tool calls; it must not inject prompt doctrine or decide what Epiphany agents do with tool results. - The May 2026 teardown/rebuild migration in `notes/epiphany-ideal-architecture-rebuild-plan.md` is closed unless a concrete regression reopens it. Preserve the new foundation: Epiphany view/coordinator policy lives in `epiphany-core`, runtime-spine owns job/result lifecycle identity through `runtime_links`, duplicate acceptance is receipt-keyed, public findings stay typed/sealed, view expansion requires a core contract, and app-server remains routing/adaptation rather than a second policy throne. - Treat Phase 5 update/distill/propose/promote/read/notification as complete enough unless a concrete regression appears. - Preserve read-only boundaries for retrieve, distill, propose, freshness, context, graphQuery, roleResult, reorientResult, and the canonical view lenses for scene, jobs, roles, planning, pressure, reorient, crrc, and coordinator. @@ -177,7 +177,7 @@ accepted_design: - Pre-compaction checkpoint intervention is now wired at token-count boundaries: when a loaded Epiphany thread crosses the existing `shouldPrepareCompaction` pressure threshold during an active turn, the harness steers the active agent once for that turn with a bounded CRRC directive to bank scratch/checkpoint/map/evidence before compaction/reorientation. A successful steer marks a turn-scoped pending compact handoff consumed at clean turn completion; this is banking the fire before the dark, not semantic acceptance, broad scheduling, or automatic implementation continuation. - Native `epiphany-mvp-status` is the first dogfood operator view: it starts or reads a thread through app-server and renders scene, planning, pressure, reorient, jobs, role result read-backs, reorient result, CRRC recommendation, heartbeat, Face bubbles, and MVP role lanes as text or machine output without becoming a state store. The old Python status module has been cut; native Rust/CultCache/CultNet surfaces are the product path. - Native status now exposes the quarantined tool/MCP spine as operator-visible read-only state. Runtime-spine counts typed tool invocation intents, receipts, and pending calls; `runtime_tool_invocation_statuses` derives intent/receipt status rows; `epiphany-runtime-spine status` prints the counts; and `epiphany-mvp-status --runtime-store ` renders a `tools` section without executing calls, accepting results, or letting Codex prompt machinery steer Epiphany agents. - - Model-tool feedback now has a typed continuation seam. `EpiphanyToolInvocationIntent` preserves the exact model `call_id` plus source model request id; provider-neutral and OpenAI request documents carry optional `previous_response_id`; the OpenAI Responses mapper emits that id as `previous_response_id`; `epiphany-model-runtime tool-followup` builds a provider-neutral follow-up request from typed tool receipts; and `epiphany-model-runtime tool-followup-turn` derives and runs that continuation through the selected provider in one runtime-owned call. The manual smoke path is model-runtime smoke -> quarantined Codex MCP spine run -> model-runtime `tool-followup`; the next organ is coordinator ownership of `tool-followup-turn`. + - Model-tool feedback now has a typed continuation seam. `EpiphanyToolInvocationIntent` preserves the exact model `call_id` plus source model request id; provider-neutral and OpenAI request documents carry optional `previous_response_id`; the OpenAI Responses mapper emits that id as `previous_response_id`; `epiphany-model-runtime tool-followup` builds a provider-neutral follow-up request from typed tool receipts; `epiphany-model-runtime tool-followup-turn` derives and runs that continuation through the selected provider in one runtime-owned call; and `epiphany-model-runtime run-worker --auto-tools --tool-adapter-bin ` now loops tool execution plus follow-up model turns before completing the worker result. - The `roles` view lens is the 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`, builds accepted evidence/observation/receipt bundles through `epiphany-core`, and rejects verification, implementation, reorientation, job binding, objective adoption, or broad authority fields. @@ -553,4 +553,4 @@ current_status: materializes complete MCP-shaped model tool calls as typed tool invocation intents, and the Codex MCP spine can consume those intents from the mixed runtime-spine store and emit typed success/failure receipts. - next_action: Continue Codex starvation and CultNet liberation by teaching the coordinator when to invoke the typed `tool-followup-turn` runtime primitive, preserving Epiphany-owned continuation policy while Codex remains only auth/model/MCP transport. + next_action: Continue Codex starvation and CultNet liberation by exercising the auto-tool worker loop in a live coordinator run, then hardening whichever typed receipt/result edge fails first. diff --git a/tools/epiphany_local_run.ps1 b/tools/epiphany_local_run.ps1 index 4be3d485..223aa881 100644 --- a/tools/epiphany_local_run.ps1 +++ b/tools/epiphany_local_run.ps1 @@ -79,6 +79,7 @@ $operatorSnapshotExe = Join-Path $TargetDir "debug\epiphany-operator-snapshot.ex $coordinatorExe = Join-Path $TargetDir "debug\epiphany-mvp-coordinator.exe" $coordinatorSmokeExe = Join-Path $TargetDir "debug\epiphany-mvp-coordinator-smoke.exe" $modelRuntimeExe = Join-Path $TargetDir "debug\epiphany-model-runtime.exe" +$toolAdapterExe = Join-Path $TargetDir "debug\epiphany-tool-codex-mcp-spine.exe" $modelProvider = "openai-codex" $operatorRunStore = Join-Path $Root ".epiphany-run\cultmesh\operator-runs.ccmp" $operatorSnapshotStore = Join-Path $Root ".epiphany-run\cultmesh\operator-snapshots.ccmp" @@ -123,6 +124,13 @@ if (-not $SkipBuild) { -WorkingDirectory $Root ` -StdoutPath (Join-Path $artifactRoot "build-model-runtime.stdout.log") ` -StderrPath (Join-Path $artifactRoot "build-model-runtime.stderr.log") + Invoke-Checked ` + -Label "build quarantined Codex MCP tool adapter" ` + -FilePath "cargo" ` + -Arguments @("build", "--manifest-path", ".\epiphany-tool-codex-mcp-spine\Cargo.toml") ` + -WorkingDirectory $Root ` + -StdoutPath (Join-Path $artifactRoot "build-tool-adapter.stdout.log") ` + -StderrPath (Join-Path $artifactRoot "build-tool-adapter.stderr.log") } } @@ -256,9 +264,13 @@ if ($Mode -eq "run") { if (-not (Test-Path -LiteralPath $modelRuntimeExe)) { throw "required runtime binary not found: $modelRuntimeExe" } + if (-not (Test-Path -LiteralPath $toolAdapterExe)) { + throw "required tool adapter binary not found: $toolAdapterExe" + } $runArgs = @( "--app-server", $codexAppServer, "--model-runtime-bin", $modelRuntimeExe, + "--tool-adapter-bin", $toolAdapterExe, "--model-provider", $modelProvider, "--codex-home", $CodexHome, "--cwd", $Workspace,