Type Epiphany worker launch cargo
This commit is contained in:
parent
bd71f1e6dc
commit
71d14ba245
9 changed files with 335 additions and 136 deletions
|
|
@ -190,6 +190,7 @@ pub use surfaces::EpiphanyReorientInput;
|
|||
pub use surfaces::EpiphanyReorientPressureLevel;
|
||||
pub use surfaces::EpiphanyReorientReason;
|
||||
pub use surfaces::EpiphanyReorientStateStatus;
|
||||
pub use surfaces::EpiphanyReorientWorkerLaunchDocument;
|
||||
pub use surfaces::EpiphanyRetrievalFreshness;
|
||||
pub use surfaces::EpiphanyRetrievalFreshnessStatus;
|
||||
pub use surfaces::EpiphanyRoleAcceptanceFinding;
|
||||
|
|
@ -203,6 +204,7 @@ pub use surfaces::EpiphanyRoleFindingInterpretation;
|
|||
pub use surfaces::EpiphanyRoleResultRoleId;
|
||||
pub use surfaces::EpiphanyRoleSelfPersistenceReview;
|
||||
pub use surfaces::EpiphanyRoleSelfPersistenceStatus;
|
||||
pub use surfaces::EpiphanyRoleWorkerLaunchDocument;
|
||||
pub use surfaces::EpiphanyScene;
|
||||
pub use surfaces::EpiphanySceneAction;
|
||||
pub use surfaces::EpiphanySceneChurn;
|
||||
|
|
@ -216,6 +218,9 @@ pub use surfaces::EpiphanySceneSource;
|
|||
pub use surfaces::EpiphanySceneStateStatus;
|
||||
pub use surfaces::EpiphanySceneStatusCount;
|
||||
pub use surfaces::EpiphanySceneSubgoal;
|
||||
pub use surfaces::EpiphanyWorkerLaunchDocument;
|
||||
pub use surfaces::REORIENT_WORKER_OUTPUT_CONTRACT_ID;
|
||||
pub use surfaces::ROLE_WORKER_OUTPUT_CONTRACT_ID;
|
||||
pub use surfaces::build_reorient_acceptance_bundle;
|
||||
pub use surfaces::build_role_acceptance_bundle;
|
||||
pub use surfaces::coordinator_automation_action;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mod reorient;
|
|||
mod role_board;
|
||||
mod role_result;
|
||||
mod scene;
|
||||
mod worker_launch;
|
||||
|
||||
pub use coordinator::EpiphanyCoordinatorAction;
|
||||
pub use coordinator::EpiphanyCoordinatorAutomationAction;
|
||||
|
|
@ -120,3 +121,8 @@ pub use scene::EpiphanySceneStateStatus;
|
|||
pub use scene::EpiphanySceneStatusCount;
|
||||
pub use scene::EpiphanySceneSubgoal;
|
||||
pub use scene::derive_scene;
|
||||
pub use worker_launch::EpiphanyReorientWorkerLaunchDocument;
|
||||
pub use worker_launch::EpiphanyRoleWorkerLaunchDocument;
|
||||
pub use worker_launch::EpiphanyWorkerLaunchDocument;
|
||||
pub use worker_launch::REORIENT_WORKER_OUTPUT_CONTRACT_ID;
|
||||
pub use worker_launch::ROLE_WORKER_OUTPUT_CONTRACT_ID;
|
||||
|
|
|
|||
122
epiphany-core/src/surfaces/worker_launch.rs
Normal file
122
epiphany-core/src/surfaces/worker_launch.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use codex_protocol::protocol::EpiphanyChurnState;
|
||||
use codex_protocol::protocol::EpiphanyCodeRef;
|
||||
use codex_protocol::protocol::EpiphanyEvidenceRecord;
|
||||
use codex_protocol::protocol::EpiphanyGraphCheckpoint;
|
||||
use codex_protocol::protocol::EpiphanyGraphFrontier;
|
||||
use codex_protocol::protocol::EpiphanyGraphs;
|
||||
use codex_protocol::protocol::EpiphanyInvariant;
|
||||
use codex_protocol::protocol::EpiphanyInvestigationCheckpoint;
|
||||
use codex_protocol::protocol::EpiphanyObservation;
|
||||
use codex_protocol::protocol::EpiphanyPlanningState;
|
||||
use codex_protocol::protocol::EpiphanyScratchPad;
|
||||
use codex_protocol::protocol::EpiphanySubgoal;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
pub const ROLE_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.role_result.v0";
|
||||
pub const REORIENT_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.reorient_result.v0";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "documentKind")]
|
||||
pub enum EpiphanyWorkerLaunchDocument {
|
||||
Role(EpiphanyRoleWorkerLaunchDocument),
|
||||
Reorient(EpiphanyReorientWorkerLaunchDocument),
|
||||
}
|
||||
|
||||
impl EpiphanyWorkerLaunchDocument {
|
||||
pub fn output_contract_id(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Role(_) => ROLE_WORKER_OUTPUT_CONTRACT_ID,
|
||||
Self::Reorient(_) => REORIENT_WORKER_OUTPUT_CONTRACT_ID,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn thread_id(&self) -> &str {
|
||||
match self {
|
||||
Self::Role(document) => &document.thread_id,
|
||||
Self::Reorient(document) => &document.thread_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EpiphanyRoleWorkerLaunchDocument {
|
||||
pub thread_id: String,
|
||||
pub role_id: String,
|
||||
pub state_revision: u64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub objective: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub active_subgoal_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub active_subgoals: Vec<EpiphanySubgoal>,
|
||||
#[serde(default)]
|
||||
pub active_graph_node_ids: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub investigation_checkpoint: Option<EpiphanyInvestigationCheckpoint>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub scratch: Option<EpiphanyScratchPad>,
|
||||
#[serde(default)]
|
||||
pub invariants: Vec<EpiphanyInvariant>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub graphs: Option<EpiphanyGraphs>,
|
||||
#[serde(default)]
|
||||
pub recent_evidence: Vec<EpiphanyEvidenceRecord>,
|
||||
#[serde(default)]
|
||||
pub recent_observations: Vec<EpiphanyObservation>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub graph_frontier: Option<EpiphanyGraphFrontier>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub graph_checkpoint: Option<EpiphanyGraphCheckpoint>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub planning: Option<EpiphanyPlanningState>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub churn: Option<EpiphanyChurnState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EpiphanyReorientWorkerLaunchDocument {
|
||||
pub thread_id: String,
|
||||
pub mode: String,
|
||||
pub checkpoint_id: String,
|
||||
pub checkpoint_kind: String,
|
||||
pub checkpoint_disposition: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub checkpoint_focus: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub checkpoint_summary: Option<String>,
|
||||
pub checkpoint_next_action: String,
|
||||
#[serde(default)]
|
||||
pub checkpoint_open_questions: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub checkpoint_evidence_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub checkpoint_code_refs: Vec<EpiphanyCodeRef>,
|
||||
#[serde(default)]
|
||||
pub decision_reasons: Vec<String>,
|
||||
pub decision_note: String,
|
||||
pub pressure_level: String,
|
||||
pub retrieval_status: String,
|
||||
pub graph_status: String,
|
||||
pub watcher_status: String,
|
||||
#[serde(default)]
|
||||
pub checkpoint_dirty_paths: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub checkpoint_changed_paths: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub scratch: Option<EpiphanyScratchPad>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub graphs: Option<EpiphanyGraphs>,
|
||||
#[serde(default)]
|
||||
pub recent_evidence: Vec<EpiphanyEvidenceRecord>,
|
||||
#[serde(default)]
|
||||
pub recent_observations: Vec<EpiphanyObservation>,
|
||||
#[serde(default)]
|
||||
pub active_frontier_node_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub linked_subgoal_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub linked_graph_node_ids: Vec<String>,
|
||||
}
|
||||
|
|
@ -381,14 +381,20 @@ inventory and ledger now exist:
|
|||
`AgentSelfPatch` document reviewed by the same contract used by agent memory.
|
||||
The legacy app-server protocol projection serializes it back to JSON only at
|
||||
the quarantine wall.
|
||||
- Second cut landed: `EpiphanyJobLaunchRequest` in core no longer accepts
|
||||
`input_json` or `output_schema_json`. It carries a typed
|
||||
`EpiphanyWorkerLaunchDocument` plus `output_contract_id`. Role and reorient
|
||||
launch helpers build typed documents directly; the old app-server protocol
|
||||
`input_json` is now hostile ingress that must parse into the typed document
|
||||
before core sees it.
|
||||
|
||||
Continue with worker launch/result cargo. Replace
|
||||
`EpiphanyJobLaunchRequest.input_json` and `output_schema_json` with typed
|
||||
launch/result documents plus schema ids in `epiphany-core`, then make
|
||||
`codex_message_processor.rs` project those documents instead of assembling
|
||||
loose JSON blobs. Do not resume Rider, Unity, Aquarium, Face, dogfood,
|
||||
planning, app, skill, marketplace, or bridge expansion until this organ is
|
||||
being cut cleanly.
|
||||
Continue at the protocol edge. Replace
|
||||
`ThreadEpiphanyJobLaunchParams.input_json` / `output_schema_json` with CultNet
|
||||
typed launch-intent messages, then replace runtime job result JSON projections
|
||||
(`runtime_job_result_to_role_json` and `runtime_job_result_to_reorient_json`)
|
||||
with typed role/reorient finding documents. Do not resume Rider, Unity,
|
||||
Aquarium, Face, dogfood, planning, app, skill, marketplace, or bridge expansion
|
||||
until this organ is being cut cleanly.
|
||||
|
||||
The Phase 6 freshness slice is landed. It exposes read-only
|
||||
`thread/epiphany/freshness` from live retrieval summaries plus graph
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ Rule: JSON is allowed at external schema/wire boundaries. Inside Epiphany, live
|
|||
## Internal JSON To Purge
|
||||
|
||||
- `vendor/codex/codex-rs/core/src/codex_thread.rs`
|
||||
- Current mechanism: `EpiphanyJobLaunchRequest.input_json` and `output_schema_json`.
|
||||
- Former mechanism: `EpiphanyJobLaunchRequest.input_json` and `output_schema_json`.
|
||||
- Real need: launch role workers with structured instructions and expected response shape.
|
||||
- What breaks if deleted: current job launch path cannot carry worker input/schema to Codex runtime.
|
||||
- Essential or bad ownership: bad ownership. Worker launch intent should be a typed document plus schema id, not two loose blobs.
|
||||
- Simpler architecture: `EpiphanyWorkerLaunchDocument` in CultCache/CultNet, with typed payload and `output_contract_id`.
|
||||
- Files: `vendor/codex/codex-rs/core/src/codex_thread.rs`, `vendor/codex/codex-rs/app-server/src/codex_message_processor.rs`, `vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs`, `epiphany-core/src/runtime_spine.rs`.
|
||||
- What broke when deleted: app-server helper tests and the manual launch handler had to stop treating launch cargo as loose JSON.
|
||||
- Essential or bad ownership: bad ownership. The core request now carries `EpiphanyWorkerLaunchDocument` plus `output_contract_id`.
|
||||
- Remaining contamination: `ThreadEpiphanyJobLaunchParams` still accepts `input_json` at the legacy app-server protocol edge and immediately parses it as hostile ingress. Final shape should be CultNet typed launch intent, not app-server JSON-RPC.
|
||||
- Files: `epiphany-core/src/surfaces/worker_launch.rs`, `vendor/codex/codex-rs/core/src/codex_thread.rs`, `vendor/codex/codex-rs/app-server/src/codex_message_processor.rs`, `vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs`.
|
||||
- `epiphany-core/src/surfaces/role_result.rs`
|
||||
- Current mechanism: `state_patch` and `self_patch` are `serde_json::Value`; `selfPatch` is manually revalidated here.
|
||||
- Real need: interpret worker findings and review bounded lane-local memory petitions.
|
||||
|
|
@ -58,7 +58,7 @@ Rule: JSON is allowed at external schema/wire boundaries. Inside Epiphany, live
|
|||
## Ranking
|
||||
|
||||
1. Done: purge `selfPatch` as internal JSON. Role-result interpretation now stores the typed `AgentSelfPatch` document and shares the agent-memory contract; JSON remains only at the legacy app-server projection.
|
||||
2. Replace worker launch `input_json` and `output_schema_json` with typed launch documents and schema ids.
|
||||
2. Partly done: core worker launch `input_json` and `output_schema_json` are replaced by typed launch documents and output contract ids. Remaining cut is the protocol edge.
|
||||
3. Replace role `statePatch` JSON with typed map/planning/graph patch documents.
|
||||
4. Seal MCP JSON behind a CultNet MCP adapter.
|
||||
5. Audit heartbeat cognition values and either type them or explicitly expire them.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -278,5 +278,5 @@ rejected_paths:
|
|||
reason: making Epiphany cleaner inside Codex still preserves the wrong organ; Codex must be starved down to OpenAI subscription auth/model transport, while Epiphany data and subsystem communication move to typed CultCache/CultNet.
|
||||
|
||||
current_status:
|
||||
summary: The Codex starvation pass has its first receipts. `notes/codex-auth-spine-inventory.md` maps the minimal vendored OpenAI auth/model-call spine; `notes/json-contamination-ledger.md` classifies the major Epiphany JSON uses by allowed edge, quarantine, or internal contamination. The first cut moved role-result `selfPatch` handling from raw JSON plus duplicated validators to the typed `AgentSelfPatch` document contract, with JSON serialization retained only at the legacy app-server protocol projection.
|
||||
next_action: Continue the liberation pass by replacing worker launch/result cargo: remove `EpiphanyJobLaunchRequest.input_json` and `output_schema_json` as internal authority, define typed launch/result documents plus schema ids in `epiphany-core`, and make `codex_message_processor.rs` project those documents instead of assembling loose JSON blobs.
|
||||
summary: The Codex starvation pass has two receipts. `notes/codex-auth-spine-inventory.md` maps the minimal vendored OpenAI auth/model-call spine; `notes/json-contamination-ledger.md` classifies the major Epiphany JSON uses by allowed edge, quarantine, or internal contamination. Role-result `selfPatch` now uses typed `AgentSelfPatch` internally. Core worker launch now uses `EpiphanyWorkerLaunchDocument` plus `output_contract_id` instead of `input_json` / `output_schema_json`; legacy app-server JSON is restricted to hostile ingress pending CultNet replacement.
|
||||
next_action: Continue the liberation pass at the protocol edge: replace `ThreadEpiphanyJobLaunchParams.input_json` / `output_schema_json` with CultNet typed launch-intent messages, then replace runtime job result JSON projections with typed role/reorient finding documents instead of `runtime_job_result_to_role_json` and `runtime_job_result_to_reorient_json`.
|
||||
|
|
|
|||
|
|
@ -549,6 +549,7 @@ use epiphany_core::EpiphanyReorientInput;
|
|||
use epiphany_core::EpiphanyReorientPressureLevel as CoreEpiphanyReorientPressureLevel;
|
||||
use epiphany_core::EpiphanyReorientReason as CoreEpiphanyReorientReason;
|
||||
use epiphany_core::EpiphanyReorientStateStatus as CoreEpiphanyReorientStateStatus;
|
||||
use epiphany_core::EpiphanyReorientWorkerLaunchDocument;
|
||||
use epiphany_core::EpiphanyRetrievalFreshness as CoreEpiphanyRetrievalFreshness;
|
||||
use epiphany_core::EpiphanyRetrievalFreshnessStatus as CoreEpiphanyRetrievalFreshnessStatus;
|
||||
use epiphany_core::EpiphanyRoleAcceptanceFinding;
|
||||
|
|
@ -562,6 +563,7 @@ use epiphany_core::EpiphanyRoleFindingInterpretation;
|
|||
use epiphany_core::EpiphanyRoleResultRoleId;
|
||||
use epiphany_core::EpiphanyRoleSelfPersistenceReview as CoreEpiphanyRoleSelfPersistenceReview;
|
||||
use epiphany_core::EpiphanyRoleSelfPersistenceStatus as CoreEpiphanyRoleSelfPersistenceStatus;
|
||||
use epiphany_core::EpiphanyRoleWorkerLaunchDocument;
|
||||
use epiphany_core::EpiphanyRuntimeJobResult;
|
||||
use epiphany_core::EpiphanyRuntimeJobSnapshot;
|
||||
use epiphany_core::EpiphanyRuntimeJobStatus;
|
||||
|
|
@ -578,6 +580,7 @@ use epiphany_core::EpiphanySceneSource as CoreEpiphanySceneSource;
|
|||
use epiphany_core::EpiphanySceneStateStatus as CoreEpiphanySceneStateStatus;
|
||||
use epiphany_core::EpiphanySceneStatusCount as CoreEpiphanySceneStatusCount;
|
||||
use epiphany_core::EpiphanySceneSubgoal as CoreEpiphanySceneSubgoal;
|
||||
use epiphany_core::EpiphanyWorkerLaunchDocument;
|
||||
use epiphany_core::build_reorient_acceptance_bundle;
|
||||
use epiphany_core::build_role_acceptance_bundle;
|
||||
use epiphany_core::coordinator_automation_action;
|
||||
|
|
@ -6381,7 +6384,7 @@ impl CodexMessageProcessor {
|
|||
linked_graph_node_ids,
|
||||
instruction,
|
||||
input_json,
|
||||
output_schema_json,
|
||||
output_schema_json: _,
|
||||
max_runtime_seconds,
|
||||
} = params;
|
||||
|
||||
|
|
@ -6406,6 +6409,19 @@ impl CodexMessageProcessor {
|
|||
}
|
||||
};
|
||||
|
||||
let launch_document =
|
||||
match serde_json::from_value::<EpiphanyWorkerLaunchDocument>(input_json) {
|
||||
Ok(document) => document,
|
||||
Err(err) => {
|
||||
self.send_invalid_request_error(
|
||||
request_id,
|
||||
format!("invalid Epiphany job launch document: {err}"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let output_contract_id = launch_document.output_contract_id().to_string();
|
||||
let changed_fields = epiphany_job_launch_changed_fields();
|
||||
let launched = match thread
|
||||
.epiphany_launch_job(EpiphanyJobLaunchRequest {
|
||||
|
|
@ -6418,8 +6434,8 @@ impl CodexMessageProcessor {
|
|||
linked_subgoal_ids,
|
||||
linked_graph_node_ids,
|
||||
instruction,
|
||||
input_json,
|
||||
output_schema_json,
|
||||
launch_document,
|
||||
output_contract_id,
|
||||
max_runtime_seconds,
|
||||
})
|
||||
.await
|
||||
|
|
@ -12841,52 +12857,51 @@ fn build_epiphany_role_launch_request(
|
|||
let owner_role = epiphany_role_owner(role_id)?;
|
||||
let linked_subgoal_ids = epiphany_active_subgoal_ids(Some(state));
|
||||
let linked_graph_node_ids = epiphany_active_graph_node_ids(Some(state));
|
||||
let (scope, authority_scope, instruction, output_schema_json) = match role_id {
|
||||
let (scope, authority_scope, instruction) = match role_id {
|
||||
ThreadEpiphanyRoleId::Imagination => (
|
||||
"role-scoped planning synthesis",
|
||||
"epiphany.role.imagination",
|
||||
build_epiphany_role_launch_instruction(role_id),
|
||||
epiphany_role_launch_output_schema(role_id),
|
||||
),
|
||||
ThreadEpiphanyRoleId::Modeling => (
|
||||
"role-scoped modeling/checkpoint maintenance",
|
||||
"epiphany.role.modeling",
|
||||
build_epiphany_role_launch_instruction(role_id),
|
||||
epiphany_role_launch_output_schema(role_id),
|
||||
),
|
||||
ThreadEpiphanyRoleId::Verification => (
|
||||
"role-scoped verification/review",
|
||||
"epiphany.role.verification",
|
||||
build_epiphany_role_launch_instruction(role_id),
|
||||
epiphany_role_launch_output_schema(role_id),
|
||||
),
|
||||
ThreadEpiphanyRoleId::Implementation | ThreadEpiphanyRoleId::Reorientation => {
|
||||
return Err(epiphany_role_binding_id(role_id).unwrap_err());
|
||||
}
|
||||
};
|
||||
let input_json = serde_json::json!({
|
||||
"threadId": thread_id,
|
||||
"roleId": epiphany_role_label(role_id),
|
||||
"stateRevision": state.revision,
|
||||
"objective": &state.objective,
|
||||
"activeSubgoalId": &state.active_subgoal_id,
|
||||
"activeSubgoals": state
|
||||
let launch_document = EpiphanyWorkerLaunchDocument::Role(EpiphanyRoleWorkerLaunchDocument {
|
||||
thread_id: thread_id.to_string(),
|
||||
role_id: epiphany_role_label(role_id).to_string(),
|
||||
state_revision: state.revision,
|
||||
objective: state.objective.clone(),
|
||||
active_subgoal_id: state.active_subgoal_id.clone(),
|
||||
active_subgoals: state
|
||||
.subgoals
|
||||
.iter()
|
||||
.filter(|subgoal| Some(subgoal.id.as_str()) == state.active_subgoal_id.as_deref())
|
||||
.collect::<Vec<_>>(),
|
||||
"activeGraphNodeIds": linked_graph_node_ids,
|
||||
"investigationCheckpoint": &state.investigation_checkpoint,
|
||||
"scratch": &state.scratch,
|
||||
"invariants": &state.invariants,
|
||||
"graphs": &state.graphs,
|
||||
"recentEvidence": state.recent_evidence.iter().take(8).collect::<Vec<_>>(),
|
||||
"recentObservations": state.observations.iter().take(8).collect::<Vec<_>>(),
|
||||
"graphFrontier": &state.graph_frontier,
|
||||
"graphCheckpoint": &state.graph_checkpoint,
|
||||
"planning": &state.planning,
|
||||
"churn": &state.churn,
|
||||
.cloned()
|
||||
.collect(),
|
||||
active_graph_node_ids: linked_graph_node_ids.clone(),
|
||||
investigation_checkpoint: state.investigation_checkpoint.clone(),
|
||||
scratch: state.scratch.clone(),
|
||||
invariants: state.invariants.clone(),
|
||||
graphs: Some(state.graphs.clone()),
|
||||
recent_evidence: state.recent_evidence.iter().take(8).cloned().collect(),
|
||||
recent_observations: state.observations.iter().take(8).cloned().collect(),
|
||||
graph_frontier: state.graph_frontier.clone(),
|
||||
graph_checkpoint: state.graph_checkpoint.clone(),
|
||||
planning: Some(state.planning.clone()),
|
||||
churn: state.churn.clone(),
|
||||
});
|
||||
let output_contract_id = launch_document.output_contract_id().to_string();
|
||||
|
||||
Ok(EpiphanyJobLaunchRequest {
|
||||
expected_revision,
|
||||
|
|
@ -12898,8 +12913,8 @@ fn build_epiphany_role_launch_request(
|
|||
linked_subgoal_ids,
|
||||
linked_graph_node_ids: epiphany_active_graph_node_ids(Some(state)),
|
||||
instruction,
|
||||
input_json,
|
||||
output_schema_json: Some(output_schema_json),
|
||||
launch_document,
|
||||
output_contract_id,
|
||||
max_runtime_seconds,
|
||||
})
|
||||
}
|
||||
|
|
@ -13149,50 +13164,50 @@ fn build_epiphany_reorient_launch_request(
|
|||
ThreadEpiphanyReorientAction::Regather => "reorient-guided checkpoint regather",
|
||||
};
|
||||
let instruction = build_epiphany_reorient_launch_instruction(decision.action);
|
||||
let input_json = serde_json::json!({
|
||||
"threadId": thread_id,
|
||||
"mode": reorient_action_label(decision.action),
|
||||
"checkpointId": checkpoint.checkpoint_id.clone(),
|
||||
"checkpointKind": checkpoint.kind.clone(),
|
||||
"checkpointDisposition": investigation_disposition_label(checkpoint.disposition),
|
||||
"checkpointFocus": checkpoint.focus.clone(),
|
||||
"checkpointSummary": checkpoint.summary.clone(),
|
||||
"checkpointNextAction": checkpoint_next_action,
|
||||
"checkpointOpenQuestions": checkpoint.open_questions.clone(),
|
||||
"checkpointEvidenceIds": checkpoint.evidence_ids.clone(),
|
||||
"checkpointCodeRefs": checkpoint
|
||||
.code_refs
|
||||
.iter()
|
||||
.map(epiphany_code_ref_json)
|
||||
.collect::<Vec<_>>(),
|
||||
"decisionReasons": decision
|
||||
.reasons
|
||||
.iter()
|
||||
.map(|reason| reorient_reason_label(*reason))
|
||||
.collect::<Vec<_>>(),
|
||||
"decisionNote": decision.note.clone(),
|
||||
"pressureLevel": pressure_level_label(decision.pressure_level),
|
||||
"retrievalStatus": retrieval_freshness_status_label(decision.retrieval_status),
|
||||
"graphStatus": graph_freshness_status_label(decision.graph_status),
|
||||
"watcherStatus": invalidation_status_label(decision.watcher_status),
|
||||
"checkpointDirtyPaths": decision
|
||||
.checkpoint_dirty_paths
|
||||
.iter()
|
||||
.map(path_to_display_string)
|
||||
.collect::<Vec<_>>(),
|
||||
"checkpointChangedPaths": decision
|
||||
.checkpoint_changed_paths
|
||||
.iter()
|
||||
.map(path_to_display_string)
|
||||
.collect::<Vec<_>>(),
|
||||
"scratch": &state.scratch,
|
||||
"graphs": &state.graphs,
|
||||
"recentEvidence": state.recent_evidence.iter().take(8).collect::<Vec<_>>(),
|
||||
"recentObservations": state.observations.iter().take(8).collect::<Vec<_>>(),
|
||||
"activeFrontierNodeIds": decision.active_frontier_node_ids.clone(),
|
||||
"linkedSubgoalIds": linked_subgoal_ids,
|
||||
"linkedGraphNodeIds": linked_graph_node_ids,
|
||||
});
|
||||
let launch_document =
|
||||
EpiphanyWorkerLaunchDocument::Reorient(EpiphanyReorientWorkerLaunchDocument {
|
||||
thread_id: thread_id.to_string(),
|
||||
mode: reorient_action_label(decision.action).to_string(),
|
||||
checkpoint_id: checkpoint.checkpoint_id.clone(),
|
||||
checkpoint_kind: checkpoint.kind.clone(),
|
||||
checkpoint_disposition: investigation_disposition_label(checkpoint.disposition)
|
||||
.to_string(),
|
||||
checkpoint_focus: Some(checkpoint.focus.clone()),
|
||||
checkpoint_summary: checkpoint.summary.clone(),
|
||||
checkpoint_next_action,
|
||||
checkpoint_open_questions: checkpoint.open_questions.clone(),
|
||||
checkpoint_evidence_ids: checkpoint.evidence_ids.clone(),
|
||||
checkpoint_code_refs: checkpoint.code_refs.clone(),
|
||||
decision_reasons: decision
|
||||
.reasons
|
||||
.iter()
|
||||
.map(|reason| reorient_reason_label(*reason).to_string())
|
||||
.collect(),
|
||||
decision_note: decision.note.clone(),
|
||||
pressure_level: pressure_level_label(decision.pressure_level).to_string(),
|
||||
retrieval_status: retrieval_freshness_status_label(decision.retrieval_status)
|
||||
.to_string(),
|
||||
graph_status: graph_freshness_status_label(decision.graph_status).to_string(),
|
||||
watcher_status: invalidation_status_label(decision.watcher_status).to_string(),
|
||||
checkpoint_dirty_paths: decision
|
||||
.checkpoint_dirty_paths
|
||||
.iter()
|
||||
.map(path_to_display_string)
|
||||
.collect(),
|
||||
checkpoint_changed_paths: decision
|
||||
.checkpoint_changed_paths
|
||||
.iter()
|
||||
.map(path_to_display_string)
|
||||
.collect(),
|
||||
scratch: state.scratch.clone(),
|
||||
graphs: Some(state.graphs.clone()),
|
||||
recent_evidence: state.recent_evidence.iter().take(8).cloned().collect(),
|
||||
recent_observations: state.observations.iter().take(8).cloned().collect(),
|
||||
active_frontier_node_ids: decision.active_frontier_node_ids.clone(),
|
||||
linked_subgoal_ids: linked_subgoal_ids.clone(),
|
||||
linked_graph_node_ids: linked_graph_node_ids.clone(),
|
||||
});
|
||||
let output_contract_id = launch_document.output_contract_id().to_string();
|
||||
|
||||
EpiphanyJobLaunchRequest {
|
||||
expected_revision,
|
||||
|
|
@ -13208,8 +13223,8 @@ fn build_epiphany_reorient_launch_request(
|
|||
.chain(decision.active_frontier_node_ids.iter().cloned()),
|
||||
),
|
||||
instruction,
|
||||
input_json,
|
||||
output_schema_json: Some(epiphany_reorient_launch_output_schema()),
|
||||
launch_document,
|
||||
output_contract_id,
|
||||
max_runtime_seconds,
|
||||
}
|
||||
}
|
||||
|
|
@ -14873,18 +14888,6 @@ fn reorient_finding_investigation_checkpoint(
|
|||
checkpoint
|
||||
}
|
||||
|
||||
fn epiphany_code_ref_json(
|
||||
code_ref: &codex_protocol::protocol::EpiphanyCodeRef,
|
||||
) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"path": path_to_display_string(code_ref.path.as_path()),
|
||||
"startLine": code_ref.start_line,
|
||||
"endLine": code_ref.end_line,
|
||||
"symbol": code_ref.symbol,
|
||||
"note": code_ref.note,
|
||||
})
|
||||
}
|
||||
|
||||
fn path_to_display_string(path: impl AsRef<Path>) -> String {
|
||||
path.as_ref().to_string_lossy().into_owned()
|
||||
}
|
||||
|
|
@ -19284,8 +19287,12 @@ mod tests {
|
|||
assert_eq!(imagination.binding_id, EPIPHANY_IMAGINATION_ROLE_BINDING_ID);
|
||||
assert_eq!(imagination.owner_role, EPIPHANY_IMAGINATION_OWNER_ROLE);
|
||||
assert_eq!(imagination.authority_scope, "epiphany.role.imagination");
|
||||
assert_eq!(imagination.input_json["roleId"], "imagination");
|
||||
assert!(imagination.input_json.get("planning").is_some());
|
||||
let EpiphanyWorkerLaunchDocument::Role(imagination_document) = &imagination.launch_document
|
||||
else {
|
||||
panic!("imagination should build a role launch document");
|
||||
};
|
||||
assert_eq!(imagination_document.role_id, "imagination");
|
||||
assert!(imagination_document.planning.is_some());
|
||||
assert!(
|
||||
imagination
|
||||
.instruction
|
||||
|
|
@ -19296,13 +19303,17 @@ mod tests {
|
|||
.instruction
|
||||
.contains("Imagination of the machine")
|
||||
);
|
||||
assert!(imagination.instruction.contains("best version of itself"));
|
||||
assert!(
|
||||
imagination
|
||||
.instruction
|
||||
.contains("reviewable `thread/epiphany/update` patch")
|
||||
);
|
||||
let imagination_schema = imagination.output_schema_json.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
imagination.output_contract_id,
|
||||
"epiphany.worker.role_result.v0"
|
||||
);
|
||||
let imagination_schema =
|
||||
epiphany_role_launch_output_schema(ThreadEpiphanyRoleId::Imagination);
|
||||
assert!(imagination_schema["properties"].get("selfPatch").is_some());
|
||||
assert!(imagination_schema["properties"].get("statePatch").is_some());
|
||||
assert_eq!(
|
||||
|
|
@ -19328,9 +19339,13 @@ mod tests {
|
|||
assert_eq!(modeling.authority_scope, "epiphany.role.modeling");
|
||||
assert_eq!(modeling.linked_subgoal_ids, vec!["sg-1".to_string()]);
|
||||
assert_eq!(modeling.linked_graph_node_ids, vec!["node-1".to_string()]);
|
||||
assert_eq!(modeling.input_json["roleId"], "modeling");
|
||||
assert!(modeling.input_json.get("graphs").is_some());
|
||||
assert!(modeling.input_json.get("recentObservations").is_some());
|
||||
let EpiphanyWorkerLaunchDocument::Role(modeling_document) = &modeling.launch_document
|
||||
else {
|
||||
panic!("modeling should build a role launch document");
|
||||
};
|
||||
assert_eq!(modeling_document.role_id, "modeling");
|
||||
assert!(modeling_document.graphs.is_some());
|
||||
assert!(modeling_document.recent_observations.is_empty());
|
||||
assert!(modeling.instruction.contains("Epiphany Persistent Memory"));
|
||||
assert!(modeling.instruction.contains("Body of the machine"));
|
||||
assert!(modeling.instruction.contains("coherent anatomy"));
|
||||
|
|
@ -19340,7 +19355,11 @@ mod tests {
|
|||
.contains("`statePatch` is part of the modeling job")
|
||||
);
|
||||
assert!(modeling.instruction.contains("optional `selfPatch`"));
|
||||
let modeling_schema = modeling.output_schema_json.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
modeling.output_contract_id,
|
||||
"epiphany.worker.role_result.v0"
|
||||
);
|
||||
let modeling_schema = epiphany_role_launch_output_schema(ThreadEpiphanyRoleId::Modeling);
|
||||
assert!(modeling_schema["properties"].get("openQuestions").is_some());
|
||||
assert!(modeling_schema["properties"].get("selfPatch").is_some());
|
||||
assert!(modeling_schema["properties"].get("statePatch").is_some());
|
||||
|
|
@ -19376,7 +19395,12 @@ mod tests {
|
|||
);
|
||||
assert_eq!(verification.owner_role, EPIPHANY_VERIFICATION_OWNER_ROLE);
|
||||
assert_eq!(verification.authority_scope, "epiphany.role.verification");
|
||||
assert_eq!(verification.input_json["roleId"], "verification");
|
||||
let EpiphanyWorkerLaunchDocument::Role(verification_document) =
|
||||
&verification.launch_document
|
||||
else {
|
||||
panic!("verification should build a role launch document");
|
||||
};
|
||||
assert_eq!(verification_document.role_id, "verification");
|
||||
assert!(
|
||||
verification
|
||||
.instruction
|
||||
|
|
@ -19384,23 +19408,17 @@ mod tests {
|
|||
);
|
||||
assert!(verification.instruction.contains("Soul of the machine"));
|
||||
assert!(verification.instruction.contains("falsify"));
|
||||
let verification_schema =
|
||||
epiphany_role_launch_output_schema(ThreadEpiphanyRoleId::Verification);
|
||||
assert!(
|
||||
verification.output_schema_json.as_ref().unwrap()["properties"]
|
||||
verification_schema["properties"]
|
||||
.get("evidenceGaps")
|
||||
.is_some()
|
||||
);
|
||||
assert!(verification_schema["properties"].get("risks").is_some());
|
||||
assert!(verification_schema["properties"].get("selfPatch").is_some());
|
||||
assert!(
|
||||
verification.output_schema_json.as_ref().unwrap()["properties"]
|
||||
.get("risks")
|
||||
.is_some()
|
||||
);
|
||||
assert!(
|
||||
verification.output_schema_json.as_ref().unwrap()["properties"]
|
||||
.get("selfPatch")
|
||||
.is_some()
|
||||
);
|
||||
assert!(
|
||||
verification.output_schema_json.as_ref().unwrap()["properties"]
|
||||
verification_schema["properties"]
|
||||
.get("statePatch")
|
||||
.is_none()
|
||||
);
|
||||
|
|
@ -19456,17 +19474,28 @@ mod tests {
|
|||
assert_eq!(request.binding_id, EPIPHANY_REORIENT_LAUNCH_BINDING_ID);
|
||||
assert_eq!(request.owner_role, EPIPHANY_REORIENT_OWNER_ROLE);
|
||||
assert_eq!(request.authority_scope, "epiphany.reorient.resume");
|
||||
let EpiphanyWorkerLaunchDocument::Reorient(reorient_document) = &request.launch_document
|
||||
else {
|
||||
panic!("reorient should build a reorient launch document");
|
||||
};
|
||||
assert_eq!(
|
||||
request.input_json["scratch"]["summary"],
|
||||
"Carry the current seam across compaction."
|
||||
reorient_document
|
||||
.scratch
|
||||
.as_ref()
|
||||
.and_then(|scratch| scratch.summary.as_deref()),
|
||||
Some("Carry the current seam across compaction.")
|
||||
);
|
||||
assert!(request.input_json.get("graphs").is_some());
|
||||
assert!(request.input_json.get("recentEvidence").is_some());
|
||||
assert!(request.input_json.get("recentObservations").is_some());
|
||||
assert!(reorient_document.graphs.is_some());
|
||||
assert!(reorient_document.recent_evidence.is_empty());
|
||||
assert!(reorient_document.recent_observations.is_empty());
|
||||
assert!(request.instruction.contains("Epiphany Persistent Memory"));
|
||||
assert!(request.instruction.contains("Life across sleep"));
|
||||
assert!(request.instruction.contains("checkpoint as the ember"));
|
||||
let schema = request.output_schema_json.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
request.output_contract_id,
|
||||
"epiphany.worker.reorient_result.v0"
|
||||
);
|
||||
let schema = epiphany_reorient_launch_output_schema();
|
||||
assert!(schema["properties"].get("openQuestions").is_some());
|
||||
assert!(schema["properties"].get("continuityRisks").is_some());
|
||||
|
||||
|
|
|
|||
45
vendor/codex/codex-rs/core/src/codex_thread.rs
vendored
45
vendor/codex/codex-rs/core/src/codex_thread.rs
vendored
|
|
@ -56,6 +56,7 @@ use codex_protocol::protocol::TokenUsageInfo;
|
|||
use codex_protocol::protocol::W3cTraceContext;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use epiphany_core::EpiphanyWorkerLaunchDocument;
|
||||
use epiphany_core::RuntimeSpineInitOptions;
|
||||
use epiphany_core::RuntimeSpineJobOptions;
|
||||
use epiphany_core::RuntimeSpineSessionOptions;
|
||||
|
|
@ -70,7 +71,6 @@ use tokio::sync::Mutex;
|
|||
use tokio::sync::watch;
|
||||
|
||||
use codex_rollout::state_db::StateDbHandle;
|
||||
use serde_json::Value;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -146,8 +146,8 @@ pub struct EpiphanyJobLaunchRequest {
|
|||
pub linked_subgoal_ids: Vec<String>,
|
||||
pub linked_graph_node_ids: Vec<String>,
|
||||
pub instruction: String,
|
||||
pub input_json: Value,
|
||||
pub output_schema_json: Option<Value>,
|
||||
pub launch_document: EpiphanyWorkerLaunchDocument,
|
||||
pub output_contract_id: String,
|
||||
pub max_runtime_seconds: Option<u64>,
|
||||
}
|
||||
|
||||
|
|
@ -793,9 +793,20 @@ fn validate_epiphany_job_launch_request(request: &EpiphanyJobLaunchRequest) -> C
|
|||
"epiphany job launch instruction must be non-empty".to_string(),
|
||||
));
|
||||
}
|
||||
if !request.input_json.is_object() {
|
||||
if request.launch_document.thread_id().trim().is_empty() {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"epiphany job launch input_json must be a JSON object".to_string(),
|
||||
"epiphany job launch document must include a thread id".to_string(),
|
||||
));
|
||||
}
|
||||
if request.output_contract_id.trim().is_empty() {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"epiphany job launch output_contract_id must be non-empty".to_string(),
|
||||
));
|
||||
}
|
||||
if request.output_contract_id != request.launch_document.output_contract_id() {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"epiphany job launch output_contract_id must match the typed launch document"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
if let Some(max_runtime_seconds) = request.max_runtime_seconds
|
||||
|
|
@ -2020,8 +2031,28 @@ mod epiphany_update_tests {
|
|||
linked_subgoal_ids: vec!["phase-6".to_string()],
|
||||
linked_graph_node_ids: vec!["runtime-spine".to_string()],
|
||||
instruction: "Model the target before implementation.".to_string(),
|
||||
input_json: serde_json::json!({ "objective": "keep state typed" }),
|
||||
output_schema_json: None,
|
||||
launch_document: EpiphanyWorkerLaunchDocument::Role(
|
||||
epiphany_core::EpiphanyRoleWorkerLaunchDocument {
|
||||
thread_id: "thread-1".to_string(),
|
||||
role_id: "modeling".to_string(),
|
||||
state_revision: 7,
|
||||
objective: Some("keep state typed".to_string()),
|
||||
active_subgoal_id: None,
|
||||
active_subgoals: Vec::new(),
|
||||
active_graph_node_ids: vec!["runtime-spine".to_string()],
|
||||
investigation_checkpoint: None,
|
||||
scratch: None,
|
||||
invariants: Vec::new(),
|
||||
graphs: None,
|
||||
recent_evidence: Vec::new(),
|
||||
recent_observations: Vec::new(),
|
||||
graph_frontier: None,
|
||||
graph_checkpoint: None,
|
||||
planning: None,
|
||||
churn: None,
|
||||
},
|
||||
),
|
||||
output_contract_id: "epiphany.worker.role_result.v0".to_string(),
|
||||
max_runtime_seconds: Some(60),
|
||||
};
|
||||
|
||||
|
|
|
|||
Reference in a new issue