Add decision-auditable reasoning context foundation
This commit is contained in:
parent
c64162175a
commit
b233d47727
18 changed files with 1377 additions and 141 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -2149,6 +2149,7 @@ name = "epiphany-openai-adapter"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cultcache-rs",
|
||||
"epiphany-model-adapter",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
@ -2280,6 +2281,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"cultcache-rs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -290,6 +290,7 @@ fn main() -> Result<()> {
|
|||
next_safe_move,
|
||||
evidence_refs,
|
||||
artifact_refs,
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
println!("runtime job completed");
|
||||
|
|
|
|||
|
|
@ -1531,6 +1531,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1693,6 +1694,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: Some(rmp_serde::to_vec_named(&candidate)?),
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1848,6 +1850,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
put_runtime_role_worker_result(store, &result)
|
||||
.map_err(|error| anyhow!("persist frontier Mind result: {error}"))?;
|
||||
|
|
@ -3216,6 +3219,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
crate::put_runtime_role_worker_result(&store, &verification_result)?;
|
||||
let soul_verdict = crate::SoulVerdictReceipt {
|
||||
|
|
@ -3356,6 +3360,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
crate::put_runtime_role_worker_result(&store, &modeling_result)?;
|
||||
let incorporation_review = crate::RepoModelAdmissionReview {
|
||||
|
|
@ -3508,6 +3513,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Review before retry.".into(),
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
|
|
@ -3684,6 +3690,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Review before retry.".into(),
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -1468,6 +1468,7 @@ mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
crate::put_runtime_role_worker_result(store, &result)?;
|
||||
crate::commit_repo_model_admission(
|
||||
|
|
@ -2174,6 +2175,7 @@ mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
crate::put_runtime_role_worker_result(&runtime_store, &verification_result)?;
|
||||
crate::put_soul_verdict_receipt(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ mod repository_readiness;
|
|||
mod resident_readiness;
|
||||
mod resident_self;
|
||||
mod runtime_spine;
|
||||
mod reasoning_context;
|
||||
mod runtime_store_backend;
|
||||
mod runtime_store_migration;
|
||||
mod runtime_worker_attempt;
|
||||
|
|
@ -988,6 +989,7 @@ pub use repository_readiness::*;
|
|||
pub use resident_readiness::*;
|
||||
pub use resident_self::*;
|
||||
pub use runtime_spine::ARCHIVED_RUNTIME_SESSION_SCHEMA_VERSION;
|
||||
pub use reasoning_context::*;
|
||||
pub use runtime_spine::ARCHIVED_RUNTIME_SESSION_TYPE;
|
||||
pub use runtime_spine::COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION;
|
||||
pub use runtime_spine::COORDINATOR_RUN_RECEIPT_TYPE;
|
||||
|
|
|
|||
1030
epiphany-core/src/reasoning_context.rs
Normal file
1030
epiphany-core/src/reasoning_context.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3625,6 +3625,7 @@ mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5694,6 +5695,7 @@ mod tests {
|
|||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
metadata: BTreeMap::new(),
|
||||
decision_context_id: None,
|
||||
})?;
|
||||
runtime.put(job_id, &role_worker_result(job_id, "modeling"))?;
|
||||
|
||||
|
|
|
|||
|
|
@ -366,6 +366,8 @@ pub struct EpiphanyRuntimeModelExecutionBinding {
|
|||
pub bound_at: String,
|
||||
#[cultcache(key = 7, default)]
|
||||
pub source_worker_job_id: Option<String>,
|
||||
#[cultcache(key = 8, default)]
|
||||
pub reasoning_basis_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
|
|
@ -645,6 +647,8 @@ pub struct EpiphanyRuntimeRoleWorkerResult {
|
|||
pub admitted_model_direction_consideration_request_id: Option<String>,
|
||||
#[cultcache(key = 34, default)]
|
||||
pub admitted_model_direction_consideration_result_msgpack: Option<Vec<u8>>,
|
||||
#[cultcache(key = 35)]
|
||||
pub decision_context_id: String,
|
||||
}
|
||||
|
||||
impl EpiphanyRuntimeRoleWorkerResult {
|
||||
|
|
@ -736,6 +740,8 @@ pub struct EpiphanyRuntimeReorientWorkerResult {
|
|||
pub item_error: Option<String>,
|
||||
#[cultcache(key = 14, default)]
|
||||
pub metadata: BTreeMap<String, String>,
|
||||
#[cultcache(key = 15)]
|
||||
pub decision_context_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
|
||||
|
|
@ -768,6 +774,8 @@ pub struct EpiphanyRuntimeJobResult {
|
|||
pub artifact_refs: Vec<String>,
|
||||
#[cultcache(key = 11, default)]
|
||||
pub metadata: BTreeMap<String, String>,
|
||||
#[cultcache(key = 12, default)]
|
||||
pub decision_context_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
|
||||
|
|
@ -982,6 +990,7 @@ pub struct RuntimeSpineJobResultOptions {
|
|||
pub next_safe_move: String,
|
||||
pub evidence_refs: Vec<String>,
|
||||
pub artifact_refs: Vec<String>,
|
||||
pub decision_context_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
@ -1117,6 +1126,9 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
|
|||
cache.register_entry_type::<EpiphanyRuntimeSession>()?;
|
||||
cache.register_entry_type::<EpiphanyRuntimeJob>()?;
|
||||
cache.register_entry_type::<EpiphanyRuntimeModelExecutionBinding>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyReasoningBasis>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyDecisionContext>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyMindCommitReceipt>()?;
|
||||
cache.register_entry_type::<EpiphanyRuntimeToolExecutionBinding>()?;
|
||||
cache.register_entry_type::<EpiphanyArchivedRuntimeSession>()?;
|
||||
cache.register_entry_type::<EpiphanyRuntimeWorkerLaunchRequest>()?;
|
||||
|
|
@ -1673,10 +1685,7 @@ pub fn open_runtime_model_execution(
|
|||
validate_non_empty(bound_at, "model execution binding time")?;
|
||||
chrono::DateTime::parse_from_rfc3339(bound_at)
|
||||
.map_err(|error| anyhow!("model execution binding time is invalid: {error}"))?;
|
||||
if provider_request.request_id != model_request.request_id
|
||||
|| provider_request.conversation_id != model_request.conversation_id
|
||||
|| provider_request.model != model_request.model
|
||||
{
|
||||
if provider_request != &epiphany_openai_adapter::request_from_native(model_request) {
|
||||
return Err(anyhow!(
|
||||
"native and provider model requests do not describe one execution"
|
||||
));
|
||||
|
|
@ -1689,9 +1698,25 @@ pub fn open_runtime_model_execution(
|
|||
require_runtime_identity_not_archived(&cache, "session", &session_options.session_id)?;
|
||||
require_runtime_identity_not_archived(&cache, "job", &job_options.job_id)?;
|
||||
require_runtime_identity_not_archived(&cache, "model-request", &model_request.request_id)?;
|
||||
let mut reasoning_basis_envelope = None;
|
||||
let source_worker_envelopes = if let Some(worker_job_id) =
|
||||
model_request.source_worker_job_id.as_deref()
|
||||
{
|
||||
let basis_id = model_request.reasoning_basis_id.as_deref().ok_or_else(|| {
|
||||
anyhow!("decision-bearing worker model execution has no reasoning basis")
|
||||
})?;
|
||||
let basis = cache
|
||||
.get::<crate::EpiphanyReasoningBasis>(basis_id)?
|
||||
.ok_or_else(|| anyhow!("model execution reasoning basis is absent"))?;
|
||||
basis.validate()?;
|
||||
if basis.pass_id != worker_job_id {
|
||||
return Err(anyhow!("model execution reasoning basis belongs to another pass"));
|
||||
}
|
||||
reasoning_basis_envelope = Some(
|
||||
cache
|
||||
.get_envelope::<crate::EpiphanyReasoningBasis>(basis_id)?
|
||||
.ok_or_else(|| anyhow!("model execution lost its reasoning basis envelope"))?,
|
||||
);
|
||||
let launch = cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(worker_job_id)?
|
||||
.ok_or_else(|| {
|
||||
|
|
@ -1819,12 +1844,16 @@ pub fn open_runtime_model_execution(
|
|||
provider: model_request.provider.clone(),
|
||||
bound_at: bound_at.to_string(),
|
||||
source_worker_job_id: model_request.source_worker_job_id.clone(),
|
||||
reasoning_basis_id: model_request.reasoning_basis_id.clone(),
|
||||
};
|
||||
let identity_envelope = cache
|
||||
.get_envelope::<EpiphanyRuntimeIdentity>(RUNTIME_IDENTITY_KEY)?
|
||||
.ok_or_else(|| anyhow!("model execution lost its exact runtime identity envelope"))?;
|
||||
let mut expected = vec![identity_envelope.clone()];
|
||||
expected.extend(source_worker_envelopes.iter().cloned());
|
||||
if let Some(envelope) = reasoning_basis_envelope.as_ref() {
|
||||
expected.push(envelope.clone());
|
||||
}
|
||||
if existing_session.is_some() {
|
||||
expected.push(
|
||||
cache
|
||||
|
|
@ -1848,6 +1877,9 @@ pub fn open_runtime_model_execution(
|
|||
.0,
|
||||
];
|
||||
replacements.extend(source_worker_envelopes);
|
||||
if let Some(envelope) = reasoning_basis_envelope {
|
||||
replacements.push(envelope);
|
||||
}
|
||||
if !runtime_spine_backing_store(store_path)?.compare_and_swap_batch(&expected, replacements)? {
|
||||
return Err(anyhow!(
|
||||
"model execution request publication lost its snapshot fence"
|
||||
|
|
@ -4417,11 +4449,13 @@ pub fn put_runtime_role_worker_result(
|
|||
validate_non_empty(&result.job_id, "role worker result job id")?;
|
||||
validate_non_empty(&result.result_id, "role worker result id")?;
|
||||
validate_non_empty(&result.role_id, "role worker result role id")?;
|
||||
validate_non_empty(&result.decision_context_id, "role worker result decision context id")?;
|
||||
if result.schema_version != RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION {
|
||||
return Err(anyhow!("role worker result schema version mismatch"));
|
||||
}
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
require_worker_decision_context(&cache, &result.decision_context_id, &result.job_id)?;
|
||||
let worker_launch = cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&result.job_id)?
|
||||
.ok_or_else(|| anyhow!("role worker result requires its immutable worker launch"))?;
|
||||
|
|
@ -9963,12 +9997,28 @@ pub fn put_runtime_reorient_worker_result(
|
|||
validate_non_empty(&result.job_id, "reorient worker result job id")?;
|
||||
validate_non_empty(&result.result_id, "reorient worker result id")?;
|
||||
validate_non_empty(&result.mode, "reorient worker result mode")?;
|
||||
validate_non_empty(&result.decision_context_id, "reorient worker result decision context id")?;
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
require_worker_decision_context(&cache, &result.decision_context_id, &result.job_id)?;
|
||||
cache.put(&result.job_id, result)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_worker_decision_context(
|
||||
cache: &cultcache_rs::CultCache,
|
||||
context_id: &str,
|
||||
worker_job_id: &str,
|
||||
) -> Result<()> {
|
||||
let context = cache
|
||||
.get::<crate::EpiphanyDecisionContext>(context_id)?
|
||||
.ok_or_else(|| anyhow!("worker result decision context is absent"))?;
|
||||
if context.native_request()?.source_worker_job_id.as_deref() != Some(worker_job_id) {
|
||||
return Err(anyhow!("worker result decision context belongs to another worker"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn runtime_reorient_worker_result(
|
||||
store_path: impl AsRef<Path>,
|
||||
job_id: &str,
|
||||
|
|
@ -12963,6 +13013,7 @@ pub fn complete_runtime_job(
|
|||
evidence_refs: options.evidence_refs,
|
||||
artifact_refs: options.artifact_refs,
|
||||
metadata: BTreeMap::new(),
|
||||
decision_context_id: options.decision_context_id,
|
||||
};
|
||||
let event = EpiphanyRuntimeEvent {
|
||||
schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(),
|
||||
|
|
@ -14648,6 +14699,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Close the session.".to_string(),
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
close_runtime_session(
|
||||
|
|
@ -14842,6 +14894,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
assert!(put_runtime_role_worker_result(&store, &result).is_err());
|
||||
assert!(
|
||||
|
|
@ -14940,6 +14993,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Retry only from this terminal process authority.".into(),
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
let claim = runtime_worker_process_claim(&store, "worker-failure-job")?
|
||||
|
|
@ -15044,6 +15098,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
assert!(put_runtime_role_worker_result(&store, &result).is_err());
|
||||
result.result_id = "other-late-result".into();
|
||||
|
|
@ -15326,6 +15381,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
let review = RepoModelAdmissionReview {
|
||||
schema_version: REPO_MODEL_ADMISSION_REVIEW_SCHEMA_VERSION.to_string(),
|
||||
|
|
@ -15991,6 +16047,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
let review = RepoModelAdmissionReview {
|
||||
schema_version: REPO_MODEL_ADMISSION_REVIEW_SCHEMA_VERSION.into(),
|
||||
|
|
@ -16440,6 +16497,7 @@ pub(crate) mod tests {
|
|||
admitted_model_direction_consideration_result_msgpack: Some(rmp_serde::to_vec_named(
|
||||
&direction,
|
||||
)?),
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
put_runtime_role_worker_result(&store, &worker_result)?;
|
||||
let mut companion_cache = runtime_spine_cache(&store)?;
|
||||
|
|
@ -17594,6 +17652,7 @@ pub(crate) mod tests {
|
|||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
metadata: BTreeMap::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
let continuation_receipt = EpiphanyCoordinatorRunReceipt {
|
||||
|
|
@ -17760,6 +17819,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
},
|
||||
)?;
|
||||
assert_eq!(
|
||||
|
|
@ -18331,6 +18391,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
put_test_non_modeling_worker_launch(&store, &verification_result.job_id, "verification")?;
|
||||
put_runtime_role_worker_result(&store, &verification_result)?;
|
||||
|
|
@ -18526,6 +18587,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
let review = RepoModelAdmissionReview {
|
||||
schema_version: REPO_MODEL_ADMISSION_REVIEW_SCHEMA_VERSION.to_string(),
|
||||
|
|
@ -18874,6 +18936,7 @@ pub(crate) mod tests {
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: "decision-context-fixture".into(),
|
||||
};
|
||||
put_test_non_modeling_worker_launch(&fixture.store, &adjacent.job_id, "verification")?;
|
||||
put_runtime_role_worker_result(&fixture.store, &adjacent)?;
|
||||
|
|
@ -18964,6 +19027,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Launch the next isolated fixture.".into(),
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
let mut cache = runtime_spine_cache(&fixture.store)?;
|
||||
|
|
@ -20497,6 +20561,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Complete the session.".to_string(),
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
|
|
@ -21104,6 +21169,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Authenticate the terminal attempt evidence.".to_string(),
|
||||
evidence_refs: vec![format!("eyes-source-{}", exact.intent_id)],
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
assert!(require_runtime_tool_execution_binding(&store, &exact.intent_id).is_err());
|
||||
|
|
@ -21279,6 +21345,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Close the session.".to_string(),
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
close_runtime_session(
|
||||
|
|
@ -21541,6 +21608,7 @@ pub(crate) mod tests {
|
|||
next_safe_move: "Launch verification.".to_string(),
|
||||
evidence_refs: vec!["evidence:model".to_string()],
|
||||
artifact_refs: vec!["artifact:model".to_string()],
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
assert_eq!(result.role, "modeling");
|
||||
|
|
@ -22735,6 +22803,7 @@ pub(crate) mod tests {
|
|||
completed_at: "2026-08-10T20:00:02Z".into(), verdict: "failed".into(),
|
||||
summary: "Typed worker attempt failed.".into(), next_safe_move: "Retry after resident closure.".into(),
|
||||
evidence_refs: Vec::new(), artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
})?;
|
||||
let before = std::fs::read(&store)?;
|
||||
assert!(archive_failed_runtime_worker_attempt(&store, &job_id,
|
||||
|
|
@ -22799,6 +22868,7 @@ pub(crate) mod tests {
|
|||
summary: "Newer typed worker attempt failed.".into(),
|
||||
next_safe_move: "Preserve the newest failed attempt.".into(),
|
||||
evidence_refs: Vec::new(), artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
})?;
|
||||
let retained = retain_failed_runtime_worker_attempts(
|
||||
&store, 1, &BTreeSet::new(), "2026-08-10T20:00:06Z")?;
|
||||
|
|
@ -22836,6 +22906,7 @@ pub(crate) mod tests {
|
|||
completed_at: "2026-08-10T21:00:02Z".into(), verdict: "pass".into(),
|
||||
summary: "Typed worker attempt completed.".into(), next_safe_move: "Retire worker scaffolding.".into(),
|
||||
evidence_refs: Vec::new(), artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
})?;
|
||||
let expected = runtime_typed_request_fulfillment(
|
||||
&store, RuntimeTypedRequestRef::ProposalModeling(&request_id))?.expect("live fulfillment");
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ pub struct EpiphanyModelRequest {
|
|||
pub output_schema_json: Option<String>,
|
||||
#[cultcache(key = 14, default)]
|
||||
pub source_worker_job_id: Option<String>,
|
||||
#[cultcache(key = 15, default)]
|
||||
pub reasoning_basis_id: Option<String>,
|
||||
}
|
||||
|
||||
impl EpiphanyModelRequest {
|
||||
|
|
@ -86,6 +88,7 @@ impl EpiphanyModelRequest {
|
|||
tools: Vec::new(),
|
||||
output_schema_json: None,
|
||||
source_worker_job_id: None,
|
||||
reasoning_basis_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ path = "src/lib.rs"
|
|||
|
||||
[dependencies]
|
||||
cultcache-rs = { path = "../vendor/cultcache-rs" }
|
||||
epiphany-model-adapter = { path = "../epiphany-model-adapter" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use cultcache_rs::DatabaseEntry;
|
||||
use epiphany_model_adapter::{EpiphanyModelInputItem, EpiphanyModelRequest};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
|
|
@ -98,6 +99,61 @@ impl EpiphanyOpenAiModelRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// The only lowering from a native model request to the OpenAI transport.
|
||||
/// Internal audit identity stays on the native request; every provider-bearing
|
||||
/// byte is derived here.
|
||||
pub fn request_from_native(request: &EpiphanyModelRequest) -> EpiphanyOpenAiModelRequest {
|
||||
EpiphanyOpenAiModelRequest {
|
||||
schema_id: OPENAI_ADAPTER_REQUEST_SCHEMA_ID.to_string(),
|
||||
request_id: request.request_id.clone(),
|
||||
conversation_id: request.conversation_id.clone(),
|
||||
model: request.model.clone(),
|
||||
instructions: request.instructions.clone(),
|
||||
input: request.input.iter().map(input_from_native).collect(),
|
||||
reasoning_effort: request.reasoning_effort.clone(),
|
||||
reasoning_summary: request.reasoning_summary.clone(),
|
||||
service_tier: request.service_tier.clone(),
|
||||
output_contract_id: request.output_contract_id.clone(),
|
||||
previous_response_id: request.previous_response_id.clone(),
|
||||
tools: request
|
||||
.tools
|
||||
.iter()
|
||||
.map(|tool| EpiphanyOpenAiToolDefinition {
|
||||
name: tool.name.clone(),
|
||||
description: tool.description.clone(),
|
||||
parameters_json: tool.parameters_json.clone(),
|
||||
})
|
||||
.collect(),
|
||||
output_schema_json: request.output_schema_json.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn input_from_native(input: &EpiphanyModelInputItem) -> EpiphanyOpenAiInputItem {
|
||||
match input {
|
||||
EpiphanyModelInputItem::UserText { text } => {
|
||||
EpiphanyOpenAiInputItem::UserText { text: text.clone() }
|
||||
}
|
||||
EpiphanyModelInputItem::AssistantText { text } => {
|
||||
EpiphanyOpenAiInputItem::AssistantText { text: text.clone() }
|
||||
}
|
||||
EpiphanyModelInputItem::ToolCall {
|
||||
call_id,
|
||||
name,
|
||||
arguments,
|
||||
} => EpiphanyOpenAiInputItem::ToolCall {
|
||||
call_id: call_id.clone(),
|
||||
name: name.clone(),
|
||||
arguments: arguments.clone(),
|
||||
},
|
||||
EpiphanyModelInputItem::ToolResult { call_id, output } => {
|
||||
EpiphanyOpenAiInputItem::ToolResult {
|
||||
call_id: call_id.clone(),
|
||||
output: output.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct EpiphanyOpenAiToolDefinition {
|
||||
pub name: String,
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ use epiphany_openai_adapter::OPENAI_ADAPTER_REQUEST_SCHEMA_ID;
|
|||
use epiphany_openai_runtime::EpiphanyOpenAiRuntimeOptions;
|
||||
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::append_requested_public_source_receipts;
|
||||
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;
|
||||
|
|
@ -658,8 +658,7 @@ fn parse_run_worker_options(args: Vec<String>) -> Result<RunWorkerCliOptions> {
|
|||
max_runtime_seconds = Some(next_value(&mut iter, "--max-runtime-seconds")?.parse()?)
|
||||
}
|
||||
"--activation-token-sha256" => {
|
||||
activation_token_sha256 =
|
||||
Some(next_value(&mut iter, "--activation-token-sha256")?)
|
||||
activation_token_sha256 = Some(next_value(&mut iter, "--activation-token-sha256")?)
|
||||
}
|
||||
other => return Err(anyhow!("unknown run-worker argument: {other}")),
|
||||
}
|
||||
|
|
@ -693,16 +692,16 @@ fn claim_and_wait_for_worker_activation(options: &RunWorkerCliOptions) -> Result
|
|||
)?;
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
let claim = epiphany_core::runtime_worker_process_claim(
|
||||
&options.store_path,
|
||||
&options.job_id,
|
||||
)?
|
||||
.ok_or_else(|| anyhow!("worker activation gate lost its process claim"))?;
|
||||
let claim =
|
||||
epiphany_core::runtime_worker_process_claim(&options.store_path, &options.job_id)?
|
||||
.ok_or_else(|| anyhow!("worker activation gate lost its process claim"))?;
|
||||
if claim.process_id != process.process_id
|
||||
|| claim.process_creation_token != process.creation_token
|
||||
|| claim.process_executable_path != process.executable_path.display().to_string()
|
||||
{
|
||||
return Err(anyhow!("worker activation gate observed a substituted process claim"));
|
||||
return Err(anyhow!(
|
||||
"worker activation gate observed a substituted process claim"
|
||||
));
|
||||
}
|
||||
match claim.status.as_str() {
|
||||
"active" => return Ok(()),
|
||||
|
|
@ -723,7 +722,9 @@ fn claim_and_wait_for_worker_activation(options: &RunWorkerCliOptions) -> Result
|
|||
"Runtime Continuity may supersede only from the terminal unactivated process claim."
|
||||
.into(),
|
||||
)?;
|
||||
return Err(anyhow!("worker activation gate expired before model/tool work"));
|
||||
return Err(anyhow!(
|
||||
"worker activation gate expired before model/tool work"
|
||||
));
|
||||
}
|
||||
status => {
|
||||
return Err(anyhow!(
|
||||
|
|
@ -875,20 +876,24 @@ async fn run_worker_launch_with_tool_continuation(
|
|||
.clone()
|
||||
.context("run-worker --auto-tools requires --tool-adapter-bin")?;
|
||||
let launch_request = load_worker_launch_request(&options.store_path, &options.job_id)?;
|
||||
let mut initial_request =
|
||||
build_worker_model_request(&launch_request, &options.provider, &options.model)?;
|
||||
let requested_public_source_intents = if launch_request
|
||||
.repo_frontier_research_request_id
|
||||
.is_some()
|
||||
{
|
||||
epiphany_core::put_runtime_requested_public_source_intents(
|
||||
&options.store_path,
|
||||
&options.job_id,
|
||||
&now(),
|
||||
)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let basis = epiphany_core::worker_reasoning_basis(&options.store_path, &launch_request)?;
|
||||
epiphany_core::put_reasoning_basis(&options.store_path, &basis)?;
|
||||
let mut initial_request = build_worker_model_request(
|
||||
&launch_request,
|
||||
&options.provider,
|
||||
&options.model,
|
||||
&basis.basis_id,
|
||||
)?;
|
||||
let requested_public_source_intents =
|
||||
if launch_request.repo_frontier_research_request_id.is_some() {
|
||||
epiphany_core::put_runtime_requested_public_source_intents(
|
||||
&options.store_path,
|
||||
&options.job_id,
|
||||
&now(),
|
||||
)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut requested_public_source_runs = Vec::new();
|
||||
for intent in &requested_public_source_intents {
|
||||
requested_public_source_runs.push(run_tool_adapter(
|
||||
|
|
@ -1241,11 +1246,7 @@ mod tests {
|
|||
#[test]
|
||||
fn worker_cli_requires_activation_capability_before_runtime_path() -> Result<()> {
|
||||
assert!(
|
||||
parse_run_worker_options(vec![
|
||||
"--job-id".into(),
|
||||
"job-without-gate".into(),
|
||||
])
|
||||
.is_err()
|
||||
parse_run_worker_options(vec!["--job-id".into(), "job-without-gate".into(),]).is_err()
|
||||
);
|
||||
let options = parse_run_worker_options(vec![
|
||||
"--job-id".into(),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ use epiphany_openai_adapter::EpiphanyOpenAiInputItem;
|
|||
use epiphany_openai_adapter::EpiphanyOpenAiModelRequest;
|
||||
use epiphany_openai_adapter::EpiphanyOpenAiStreamEvent;
|
||||
use epiphany_openai_adapter::EpiphanyOpenAiStreamPayload;
|
||||
use epiphany_openai_adapter::EpiphanyOpenAiToolDefinition;
|
||||
use epiphany_openai_codex_spine::EpiphanyCodexOpenAiTransport;
|
||||
use epiphany_openai_codex_spine::EpiphanyResponsesFrameObservation;
|
||||
use epiphany_openai_codex_spine::auth_manager;
|
||||
|
|
@ -46,11 +45,12 @@ use epiphany_openai_codex_spine::status_from_auth_manager;
|
|||
use epiphany_tool_adapter::EPIPHANY_TOOL_RUNTIME_ADAPTER_ID;
|
||||
use epiphany_tool_adapter::EpiphanyToolInvocationIntent;
|
||||
use epiphany_tool_adapter::EpiphanyToolInvocationReceipt;
|
||||
use epiphany_tool_adapter::receipt_output_for_model;
|
||||
use epiphany_tool_adapter::tool_invocation_receipt_key;
|
||||
use serde::Deserialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::de::MapAccess;
|
||||
use serde::de::SeqAccess;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::de::Visitor;
|
||||
use sha2::Digest;
|
||||
|
||||
|
|
@ -332,8 +332,14 @@ pub async fn run_worker_launch(
|
|||
options.job_id
|
||||
));
|
||||
}
|
||||
let model_request =
|
||||
build_worker_model_request(&launch_request, &options.provider, &options.model)?;
|
||||
let basis = epiphany_core::worker_reasoning_basis(&options.store_path, &launch_request)?;
|
||||
epiphany_core::put_reasoning_basis(&options.store_path, &basis)?;
|
||||
let model_request = build_worker_model_request(
|
||||
&launch_request,
|
||||
&options.provider,
|
||||
&options.model,
|
||||
&basis.basis_id,
|
||||
)?;
|
||||
let openai_options = EpiphanyOpenAiRuntimeOptions {
|
||||
store_path: options.store_path.clone(),
|
||||
codex_home: options.codex_home,
|
||||
|
|
@ -489,6 +495,7 @@ pub fn record_openai_events(
|
|||
.to_string(),
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
|
|
@ -597,7 +604,11 @@ pub fn build_worker_model_request(
|
|||
launch_request: &EpiphanyRuntimeWorkerLaunchRequest,
|
||||
provider: &str,
|
||||
model: &str,
|
||||
reasoning_basis_id: &str,
|
||||
) -> Result<EpiphanyModelRequest> {
|
||||
if reasoning_basis_id.trim().is_empty() {
|
||||
return Err(anyhow!("worker model request requires a reasoning basis"));
|
||||
}
|
||||
let launch_document = launch_request.launch_document()?;
|
||||
let output_schema_json = worker_output_schema_json(launch_request, &launch_document)?;
|
||||
let request_id = format!(
|
||||
|
|
@ -632,6 +643,7 @@ pub fn build_worker_model_request(
|
|||
request.output_contract_id = Some(launch_request.output_contract_id.clone());
|
||||
request.output_schema_json = Some(output_schema_json);
|
||||
request.source_worker_job_id = Some(launch_request.job_id.clone());
|
||||
request.reasoning_basis_id = Some(reasoning_basis_id.to_string());
|
||||
if matches!(
|
||||
launch_request.binding_id.as_str(),
|
||||
epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID
|
||||
|
|
@ -650,6 +662,8 @@ pub fn complete_worker_job_from_assistant_text(
|
|||
openai_summary: &EpiphanyOpenAiRuntimeRunSummary,
|
||||
assistant_text: &str,
|
||||
) -> Result<epiphany_core::EpiphanyRuntimeJobResult> {
|
||||
let decision_context =
|
||||
epiphany_core::seal_model_decision_context(store_path.as_ref(), openai_request_id)?;
|
||||
let launch_document = launch_request.launch_document()?;
|
||||
let parsed_result = parse_worker_result_ingress(&launch_document, assistant_text);
|
||||
let parse_error = parsed_result
|
||||
|
|
@ -718,6 +732,7 @@ pub fn complete_worker_job_from_assistant_text(
|
|||
.as_ref(),
|
||||
&completed_at,
|
||||
&result_id,
|
||||
&decision_context.context_id,
|
||||
parsed,
|
||||
evidence_refs.clone(),
|
||||
artifact_refs.clone(),
|
||||
|
|
@ -728,6 +743,7 @@ pub fn complete_worker_job_from_assistant_text(
|
|||
let typed_result = reorient_worker_result_from_ingress(
|
||||
launch_request,
|
||||
&result_id,
|
||||
&decision_context.context_id,
|
||||
parsed,
|
||||
artifact_refs.clone(),
|
||||
);
|
||||
|
|
@ -751,6 +767,7 @@ pub fn complete_worker_job_from_assistant_text(
|
|||
next_safe_move,
|
||||
evidence_refs,
|
||||
artifact_refs,
|
||||
decision_context_id: Some(decision_context.context_id),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -772,6 +789,7 @@ pub fn fail_worker_job(
|
|||
next_safe_move,
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -938,7 +956,7 @@ pub fn build_tool_followup_model_request(
|
|||
});
|
||||
input.push(EpiphanyModelInputItem::ToolResult {
|
||||
call_id,
|
||||
output: tool_receipt_output_for_model(&intent, &receipt),
|
||||
output: receipt_output_for_model(&intent, &receipt),
|
||||
});
|
||||
}
|
||||
followup.input = input;
|
||||
|
|
@ -1018,9 +1036,7 @@ pub fn append_requested_public_source_receipts(
|
|||
.as_deref()
|
||||
.ok_or_else(|| anyhow!("requested public source intent has no call id"))?;
|
||||
let receipt = cache
|
||||
.get::<EpiphanyToolInvocationReceipt>(&tool_invocation_receipt_key(
|
||||
&intent.intent_id,
|
||||
))?
|
||||
.get::<EpiphanyToolInvocationReceipt>(&tool_invocation_receipt_key(&intent.intent_id))?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"requested public source intent {:?} has no terminal receipt",
|
||||
|
|
@ -1044,31 +1060,12 @@ pub fn append_requested_public_source_receipts(
|
|||
});
|
||||
request.input.push(EpiphanyModelInputItem::ToolResult {
|
||||
call_id: call_id.to_string(),
|
||||
output: tool_receipt_output_for_model(intent, &receipt),
|
||||
output: receipt_output_for_model(intent, &receipt),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tool_receipt_output_for_model(
|
||||
intent: &EpiphanyToolInvocationIntent,
|
||||
receipt: &EpiphanyToolInvocationReceipt,
|
||||
) -> String {
|
||||
if let Some(result) = receipt.result_json.as_ref() {
|
||||
return result.clone();
|
||||
}
|
||||
serde_json::json!({
|
||||
"status": receipt.status,
|
||||
"adapter": receipt.adapter,
|
||||
"server": receipt.server,
|
||||
"toolName": receipt.tool_name,
|
||||
"intentId": intent.intent_id,
|
||||
"receiptId": receipt.receipt_id,
|
||||
"error": receipt.error,
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn default_options(
|
||||
store_path: PathBuf,
|
||||
codex_home: PathBuf,
|
||||
|
|
@ -1170,6 +1167,7 @@ pub fn model_request_from_openai_request(
|
|||
previous_response_id: request.previous_response_id.clone(),
|
||||
output_schema_json: request.output_schema_json.clone(),
|
||||
source_worker_job_id: None,
|
||||
reasoning_basis_id: None,
|
||||
tools: request
|
||||
.tools
|
||||
.iter()
|
||||
|
|
@ -1185,33 +1183,7 @@ pub fn model_request_from_openai_request(
|
|||
pub fn openai_request_from_model_request(
|
||||
request: &EpiphanyModelRequest,
|
||||
) -> EpiphanyOpenAiModelRequest {
|
||||
EpiphanyOpenAiModelRequest {
|
||||
schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_REQUEST_SCHEMA_ID.to_string(),
|
||||
request_id: request.request_id.clone(),
|
||||
conversation_id: request.conversation_id.clone(),
|
||||
model: request.model.clone(),
|
||||
instructions: request.instructions.clone(),
|
||||
input: request
|
||||
.input
|
||||
.iter()
|
||||
.map(openai_input_from_model_input)
|
||||
.collect(),
|
||||
reasoning_effort: request.reasoning_effort.clone(),
|
||||
reasoning_summary: request.reasoning_summary.clone(),
|
||||
service_tier: request.service_tier.clone(),
|
||||
output_contract_id: request.output_contract_id.clone(),
|
||||
previous_response_id: request.previous_response_id.clone(),
|
||||
output_schema_json: request.output_schema_json.clone(),
|
||||
tools: request
|
||||
.tools
|
||||
.iter()
|
||||
.map(|tool| EpiphanyOpenAiToolDefinition {
|
||||
name: tool.name.clone(),
|
||||
description: tool.description.clone(),
|
||||
parameters_json: tool.parameters_json.clone(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
epiphany_openai_adapter::request_from_native(request)
|
||||
}
|
||||
|
||||
fn repository_source_tools() -> Vec<EpiphanyModelToolDefinition> {
|
||||
|
|
@ -1318,32 +1290,6 @@ fn model_input_from_openai_input(input: &EpiphanyOpenAiInputItem) -> EpiphanyMod
|
|||
}
|
||||
}
|
||||
|
||||
fn openai_input_from_model_input(input: &EpiphanyModelInputItem) -> EpiphanyOpenAiInputItem {
|
||||
match input {
|
||||
EpiphanyModelInputItem::UserText { text } => {
|
||||
EpiphanyOpenAiInputItem::UserText { text: text.clone() }
|
||||
}
|
||||
EpiphanyModelInputItem::AssistantText { text } => {
|
||||
EpiphanyOpenAiInputItem::AssistantText { text: text.clone() }
|
||||
}
|
||||
EpiphanyModelInputItem::ToolCall {
|
||||
call_id,
|
||||
name,
|
||||
arguments,
|
||||
} => EpiphanyOpenAiInputItem::ToolCall {
|
||||
call_id: call_id.clone(),
|
||||
name: name.clone(),
|
||||
arguments: arguments.clone(),
|
||||
},
|
||||
EpiphanyModelInputItem::ToolResult { call_id, output } => {
|
||||
EpiphanyOpenAiInputItem::ToolResult {
|
||||
call_id: call_id.clone(),
|
||||
output: output.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn model_event_from_openai_event(
|
||||
provider: &str,
|
||||
event: &EpiphanyOpenAiStreamEvent,
|
||||
|
|
@ -1802,6 +1748,7 @@ fn role_worker_result_from_ingress(
|
|||
>,
|
||||
completed_at: &str,
|
||||
result_id: &str,
|
||||
decision_context_id: &str,
|
||||
result: &RoleWorkerResultIngress,
|
||||
runtime_evidence_ids: Vec<String>,
|
||||
artifact_refs: Vec<String>,
|
||||
|
|
@ -2121,6 +2068,7 @@ fn role_worker_result_from_ingress(
|
|||
admitted_model_direction_consideration_context
|
||||
.map(|context| context.request.request_id.clone()),
|
||||
admitted_model_direction_consideration_result_msgpack,
|
||||
decision_context_id: decision_context_id.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2181,12 +2129,14 @@ pub fn failed_frontier_planning_role_result(
|
|||
imagination_consideration_candidate_msgpack: None,
|
||||
admitted_model_direction_consideration_request_id: None,
|
||||
admitted_model_direction_consideration_result_msgpack: None,
|
||||
decision_context_id: String::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn reorient_worker_result_from_ingress(
|
||||
launch_request: &EpiphanyRuntimeWorkerLaunchRequest,
|
||||
result_id: &str,
|
||||
decision_context_id: &str,
|
||||
result: &ReorientWorkerResultIngress,
|
||||
artifact_refs: Vec<String>,
|
||||
) -> EpiphanyRuntimeReorientWorkerResult {
|
||||
|
|
@ -2211,6 +2161,7 @@ fn reorient_worker_result_from_ingress(
|
|||
continuity_risks: clean_string_vec(&result.continuity_risks),
|
||||
item_error: None,
|
||||
metadata: std::collections::BTreeMap::new(),
|
||||
decision_context_id: decision_context_id.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2301,7 +2252,9 @@ impl<'de> Visitor<'de> for UniqueJsonValueVisitor {
|
|||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
|
||||
Ok(UniqueJsonValue(serde_json::Value::String(value.to_string())))
|
||||
Ok(UniqueJsonValue(serde_json::Value::String(
|
||||
value.to_string(),
|
||||
)))
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
|
||||
|
|
@ -2334,9 +2287,7 @@ impl<'de> Visitor<'de> for UniqueJsonValueVisitor {
|
|||
let mut values = serde_json::Map::new();
|
||||
while let Some((key, value)) = object.next_entry::<String, UniqueJsonValue>()? {
|
||||
if values.insert(key.clone(), value.0).is_some() {
|
||||
return Err(serde::de::Error::custom(format!(
|
||||
"duplicate field `{key}`"
|
||||
)));
|
||||
return Err(serde::de::Error::custom(format!("duplicate field `{key}`")));
|
||||
}
|
||||
}
|
||||
Ok(UniqueJsonValue(serde_json::Value::Object(values)))
|
||||
|
|
@ -2642,6 +2593,7 @@ mod tests {
|
|||
None,
|
||||
"2026-08-11T08:00:02Z",
|
||||
"result-proposal-job-1",
|
||||
"decision-context-test",
|
||||
&ingress,
|
||||
vec!["openai-request:proposal".into()],
|
||||
Vec::new(),
|
||||
|
|
@ -2758,7 +2710,12 @@ mod tests {
|
|||
repo_frontier_verdict_modeling_authority_msgpack: None,
|
||||
};
|
||||
|
||||
let request = build_worker_model_request(&launch, DEFAULT_MODEL_PROVIDER, "gpt-5.4")?;
|
||||
let request = build_worker_model_request(
|
||||
&launch,
|
||||
DEFAULT_MODEL_PROVIDER,
|
||||
"gpt-5.4",
|
||||
"reasoning-basis-test",
|
||||
)?;
|
||||
let schema: serde_json::Value = serde_json::from_str(
|
||||
request
|
||||
.output_schema_json
|
||||
|
|
@ -2818,6 +2775,7 @@ mod tests {
|
|||
None,
|
||||
"2026-07-15T10:00:00Z",
|
||||
"verification-result-1",
|
||||
"decision-context-test",
|
||||
&parsed,
|
||||
vec!["openai-request:verification-request-1".to_string()],
|
||||
Vec::new(),
|
||||
|
|
@ -2899,6 +2857,7 @@ mod tests {
|
|||
None,
|
||||
"2026-07-15T10:00:00Z",
|
||||
"planning-result-1",
|
||||
"decision-context-test",
|
||||
&parsed,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
|
|
@ -3032,6 +2991,7 @@ mod tests {
|
|||
None,
|
||||
"2026-07-15T10:00:00Z",
|
||||
"consideration-result-1",
|
||||
"decision-context-test",
|
||||
&parsed,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
|
|
@ -3227,6 +3187,7 @@ mod tests {
|
|||
None,
|
||||
"2026-07-15T10:00:00Z",
|
||||
"mind-result-1",
|
||||
"decision-context-test",
|
||||
&parsed,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
|
|
@ -3521,8 +3482,35 @@ mod tests {
|
|||
},
|
||||
)?;
|
||||
let launch_request = load_worker_launch_request(&store, "worker-job-1")?;
|
||||
let model_request =
|
||||
build_worker_model_request(&launch_request, DEFAULT_MODEL_PROVIDER, "gpt-5.4")?;
|
||||
let reasoning_basis = epiphany_core::worker_reasoning_basis(&store, &launch_request)?;
|
||||
epiphany_core::put_reasoning_basis(&store, &reasoning_basis)?;
|
||||
let model_request = build_worker_model_request(
|
||||
&launch_request,
|
||||
DEFAULT_MODEL_PROVIDER,
|
||||
"gpt-5.4",
|
||||
&reasoning_basis.basis_id,
|
||||
)?;
|
||||
let provider_request = openai_request_from_model_request(&model_request);
|
||||
epiphany_core::open_runtime_model_execution(
|
||||
&store,
|
||||
RuntimeSpineSessionOptions {
|
||||
session_id: "openai-worker-session-modeling-checkpoint-worker".into(),
|
||||
objective: "Run the exact Modeling pass.".into(),
|
||||
created_at: "2026-08-14T00:00:00Z".into(),
|
||||
coordinator_note: "decision context fixture".into(),
|
||||
},
|
||||
RuntimeSpineJobOptions {
|
||||
job_id: "openai-worker-worker-job-1".into(),
|
||||
session_id: "openai-worker-session-modeling-checkpoint-worker".into(),
|
||||
role: "openai-model-adapter".into(),
|
||||
created_at: "2026-08-14T00:00:00Z".into(),
|
||||
summary: "Execute the Modeling request.".into(),
|
||||
artifact_refs: Vec::new(),
|
||||
},
|
||||
&model_request,
|
||||
&provider_request,
|
||||
"2026-08-14T00:00:00Z",
|
||||
)?;
|
||||
assert_eq!(
|
||||
model_request.output_contract_id.as_deref(),
|
||||
Some(epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID)
|
||||
|
|
@ -3718,8 +3706,12 @@ mod tests {
|
|||
},
|
||||
)?;
|
||||
let launch_request = load_worker_launch_request(&store, "verification-job-1")?;
|
||||
let model_request =
|
||||
build_worker_model_request(&launch_request, DEFAULT_MODEL_PROVIDER, "gpt-5.4")?;
|
||||
let model_request = build_worker_model_request(
|
||||
&launch_request,
|
||||
DEFAULT_MODEL_PROVIDER,
|
||||
"gpt-5.4",
|
||||
"reasoning-basis-test",
|
||||
)?;
|
||||
let tool_names = model_request
|
||||
.tools
|
||||
.iter()
|
||||
|
|
@ -3752,12 +3744,18 @@ mod tests {
|
|||
research_launch.binding_id = epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID.to_string();
|
||||
research_launch.role = epiphany_core::EPIPHANY_RESEARCH_OWNER_ROLE.to_string();
|
||||
research_launch.authority_scope = "epiphany.role.research".to_string();
|
||||
let research_request =
|
||||
build_worker_model_request(&research_launch, DEFAULT_MODEL_PROVIDER, "gpt-5.4")?;
|
||||
assert!(!research_request
|
||||
.tools
|
||||
.iter()
|
||||
.any(|tool| tool.name == "mcp__epiphany_public__github_file"));
|
||||
let research_request = build_worker_model_request(
|
||||
&research_launch,
|
||||
DEFAULT_MODEL_PROVIDER,
|
||||
"gpt-5.4",
|
||||
"reasoning-basis-test",
|
||||
)?;
|
||||
assert!(
|
||||
!research_request
|
||||
.tools
|
||||
.iter()
|
||||
.any(|tool| tool.name == "mcp__epiphany_public__github_file")
|
||||
);
|
||||
assert!(
|
||||
research_request
|
||||
.instructions
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ path = "src/lib.rs"
|
|||
[dependencies]
|
||||
cultcache-rs = { path = "../vendor/cultcache-rs" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,27 @@ pub fn tool_invocation_receipt_key(intent_id: &str) -> String {
|
|||
format!("receipt:{intent_id}")
|
||||
}
|
||||
|
||||
/// Canonical lowering of a governed receipt into the exact tool-result text
|
||||
/// supplied to a model. Decision audit and execution must share this owner.
|
||||
pub fn receipt_output_for_model(
|
||||
intent: &EpiphanyToolInvocationIntent,
|
||||
receipt: &EpiphanyToolInvocationReceipt,
|
||||
) -> String {
|
||||
if let Some(result) = receipt.result_json.as_ref() {
|
||||
return result.clone();
|
||||
}
|
||||
serde_json::json!({
|
||||
"status": receipt.status,
|
||||
"adapter": receipt.adapter,
|
||||
"server": receipt.server,
|
||||
"toolName": receipt.tool_name,
|
||||
"intentId": intent.intent_id,
|
||||
"receiptId": receipt.receipt_id,
|
||||
"error": receipt.error,
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.tool_capability.v0",
|
||||
|
|
|
|||
|
|
@ -4516,3 +4516,23 @@ zero.
|
|||
before network under `--network none`, preserved byte-identical runtime state,
|
||||
and emitted no receipt. Together these close the immutable public Eyes
|
||||
success/denial pair.
|
||||
## Decision-auditable concurrent Mind foundation (2026-08-14)
|
||||
|
||||
The migration seals a reasoning pass before inference. One
|
||||
`EpiphanyReasoningBasis` content-addresses the exact typed source envelopes and
|
||||
one closed projection variant. Every decision-bearing native model request
|
||||
binds that basis. The OpenAI request is derived from the native request by the
|
||||
adapter; callers cannot author an independent provider request.
|
||||
|
||||
At terminal inference, `EpiphanyDecisionContext` records the final native and
|
||||
provider requests plus the ordered governed tool intent/receipt versions that
|
||||
the final request actually contains. Validation compares exact call identity,
|
||||
tool name, arguments, lowered result bytes, runtime model binding, worker,
|
||||
session, and basis. Stream deltas are not decision authority.
|
||||
|
||||
`commit_mind_mutation` accepts exact strong-read envelopes and exact writes.
|
||||
CultCache batch CAS merges disjoint identities, refuses changed strong reads or
|
||||
same-identity divergence, and writes an immutable Mind commit receipt with the
|
||||
mutation. It never rebases model output. This foundation does not yet replace
|
||||
persisted thread state or aggregate RepoModel; those obsolete authorities are
|
||||
the next hard deletion line.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,17 @@ The five-day shakedown remains paused for architectural consolidation. The
|
|||
latest behavior cut is clean and pushed on `codex/epiphany-shakedown-live` at
|
||||
exact `f90f1186`.
|
||||
|
||||
The active campaign is now the hard Decision-Auditable Concurrent Mind
|
||||
migration. Its first foundation is implemented locally: typed content-addressed
|
||||
reasoning bases preserve exact source envelopes and a closed projection;
|
||||
terminal decision contexts preserve the exact native request, the provider
|
||||
request derived from it, and every ordered governed tool intent/receipt actually
|
||||
fed to the model. Worker model requests require a sealed basis, and structured
|
||||
role/reorient results plus their generic job result bind the terminal context.
|
||||
Exact-envelope batch CAS merges disjoint Mind documents and explicitly refuses
|
||||
same-identity or changed-strong-read conflicts. Model failures, Persona stages,
|
||||
retention, keyed canonical Mind state, and keyed RepoModel remain to be cut.
|
||||
|
||||
Operational correction: the original c011 resident and Heartbeat containers
|
||||
are not alive; both exited 255 on 2026-08-11. Their named volumes are quiescent
|
||||
and remain untouched. Historical c011 zero-zombie and organ-circuit evidence is
|
||||
|
|
@ -109,16 +120,23 @@ unchanged; the lifecycle authorities above remain separate.
|
|||
|
||||
## Acceptance gates before shakedown resumes
|
||||
|
||||
1. Generalize exact current-work lifecycle ownership across Modeling and
|
||||
Verification so neither family can repeat Research's stale-role-lane split.
|
||||
2. Run one fresh-repository source -> Hands -> Soul -> Modeling -> Mind -> Self
|
||||
capstone before claiming organism-level readiness.
|
||||
1. Every model-authored terminal decision or typed failure recovers its exact
|
||||
reasoning basis and terminal request after transcript/session retention.
|
||||
2. Persisted thread state, aggregate RepoModel revision/hash, timestamps,
|
||||
events, and role lanes can no longer manufacture or suppress current work.
|
||||
3. Concurrent disjoint Persona, Hands, Modeling, evidence, and Verification
|
||||
mutations merge; same semantic identity and changed strong reads refuse
|
||||
without partial writes or silent model-output rebasing.
|
||||
4. Run the fresh-store concurrent decision-audit capstone, including Body ->
|
||||
Modeling without Eyes and Eyes only for explicit external-evidence work.
|
||||
|
||||
## Immediate next action
|
||||
|
||||
Map Modeling and Verification's exact causal currency, then give each family
|
||||
one typed continuation owner consumed by Resident Self, coordinator policy, and
|
||||
status. Preserve their distinct invariants; do not create a generic registry or
|
||||
let the mutable role-board projection decide current work. Replay the accepted
|
||||
circuits before the fresh-repository capstone. Do not touch the original c011
|
||||
volumes, add capability surfaces, or build on Yggdrasil.
|
||||
Finish decision-context ownership before normalizing state: bind model-backed
|
||||
failures and all three Persona stages, make their terminal commits atomic, and
|
||||
make session/worker/Persona retention preserve the Basis -> Context -> decision
|
||||
route after streams and transport scaffolding disappear. Then delete persisted
|
||||
thread-state authority and split canonical Mind and RepoModel into semantic-key
|
||||
documents under exact-envelope batch CAS. There is no writable-store migrator
|
||||
or compatibility read path. Do not touch the original c011 volumes, resume the
|
||||
shakedown, add capability surfaces, or build on Yggdrasil.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ experiment:
|
|||
purpose: Native, inspectable agent organism with typed state and explicit organ authority.
|
||||
|
||||
objective:
|
||||
current: Consolidate the architecture exposed by the paused five-day shakedown before any new capability or live-body replacement.
|
||||
current: Migrate the paused shakedown body to a decision-auditable concurrent Mind before any new capability or live-body replacement.
|
||||
|
||||
architecture:
|
||||
body: Repository, runtime, service processes, stores, interfaces, and deployment substrate.
|
||||
|
|
@ -70,7 +70,7 @@ current_status:
|
|||
yggdrasil_epiphany_service_contract: repaired_in_gamecult_ops_812b427_not_installed
|
||||
resident_physiology: historical_c011_exact_465af24d_zero_zombie_proof_accepted_current_containers_exited_255
|
||||
unresolved_authority: Coordinator receipt, Heartbeat pulse-artifact, genuine-idle and active resource plateaus, exact-grant terminality, restart-persistent cooldown refusal, schedule/action issuance cardinality, runtime-owned typed fulfillment, all three packaged fulfilled-before-receipt lanes, genuine packaged Imagination option generation, narrow model-generation retention, published model-turn JSON ingress, semantic logical/physical retention, Persona conversation retention, stable Starfire firewall ownership, completed model-adapter session archival, normal coordinator lifecycle opening/error closure/legacy repair/archival, co-resident resident lifecycle retention, authenticated outer-worker attempt retention, deployed supervisor/projector journald ownership, Docker named-volume atomic snapshot visibility, deterministic incremental release construction, and canonical-brake refusal of direct daemon lifecycle actuation are accepted. Starfire's Docker Desktop F: bind is rejected for mutable authority after an exact packaged lockless reader observed an empty published snapshot during writer interruption. Historical migrated semantic attempts without recorded physical namespace remain durable and non-retirable. One completed Discord consequence remains blocked by the absent Yggdrasil credential. Exact aa841873 also makes legacy coordinator repair one full-snapshot transaction so a later hostile family cannot leave an earlier partial repair.
|
||||
next_action: Generalize current-work lifecycle ownership for Modeling and Verification before another live discovery dresses the stale-role-lane bug in a new suit. Map each family's exact causal currency, then make coordinator, status, and Resident Self consume one typed owner per family without a generic registry or shared mutable role projection. Replay the accepted circuits, then run the fresh-repository source-to-Hands-to-Soul-to-Modeling-to-Mind-to-Self capstone.
|
||||
next_action: Complete the hard Decision-Auditable Concurrent Mind cut. The first foundation now seals content-addressed typed reasoning bases and terminal decision contexts, derives the provider request from the native request through one lowering owner, authenticates exact governed tool observations, and gives disjoint Mind writes exact-envelope batch CAS. Next bind model-backed failure and Persona stage outcomes, preserve contexts through retention, then replace persisted thread state and aggregate RepoModel authority with keyed Mind documents and state-derived obligations. No migrator or dual-read path; historical stores remain read-only.
|
||||
|
||||
active_subgoals:
|
||||
- Prove Persona, Self, Eyes, Modeling, Imagination, Hands, Soul, Mind, Continuity, and nervous-system behavior individually.
|
||||
|
|
|
|||
Reference in a new issue