Auto-continue worker tool calls

This commit is contained in:
Meta Vens 2026-05-28 12:18:58 +01:00
commit 71d3cd1f3e
6 changed files with 230 additions and 23 deletions

View file

@ -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<String>,
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<Value> {
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<Value> {
);
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<Value> {
);
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<Value> {
"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<Value> {
"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<PathBuf>
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<String> {
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<Value> {
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()))?;

View file

@ -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<PathBuf>,
cwd: Option<PathBuf>,
max_tool_rounds: usize,
}
struct ToolFollowupCliOptions {
@ -354,6 +369,10 @@ fn parse_run_worker_options(args: Vec<String>) -> Result<RunWorkerCliOptions> {
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<String>) -> Result<RunWorkerCliOptions> {
"--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<String>) -> Result<RunWorkerCliOptions> {
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<String>) -> Result<ToolFollowupCliOptio
})
}
async fn run_worker_launch_with_tool_continuation(
options: RunWorkerCliOptions,
) -> Result<serde_json::Value> {
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(),
&current_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, &current_request_id)?;
let worker_result = complete_worker_job_from_assistant_text(
&options.store_path,
&launch_request,
&current_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<serde_json::Value> {
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<String>) -> Result<ToolFollowupTurnCliOptions> {
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 <model-turn|run-worker|tool-followup|tool-followup-turn|smoke> [--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 <model-turn|run-worker|tool-followup|tool-followup-turn|smoke> [--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]"
}

View file

@ -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 <path>` 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 <path>` 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 <path>` 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.

Binary file not shown.

View file

@ -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 <path>` 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 <path>` 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.

View file

@ -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,