Auto-continue worker tool calls
This commit is contained in:
parent
436de94cf2
commit
71d3cd1f3e
6 changed files with 230 additions and 23 deletions
|
|
@ -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()))?;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
¤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<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]"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Reference in a new issue