Preserve decision context through failure and retention
This commit is contained in:
parent
b233d47727
commit
9036aaa6bb
5 changed files with 109 additions and 12 deletions
|
|
@ -422,6 +422,10 @@ pub struct EpiphanyArchivedRuntimeSession {
|
|||
pub retired_envelope_count: u64,
|
||||
#[cultcache(key = 11)]
|
||||
pub retired_chain_digest: String,
|
||||
#[cultcache(key = 12, default)]
|
||||
pub reasoning_basis_ids: Vec<String>,
|
||||
#[cultcache(key = 13, default)]
|
||||
pub decision_context_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DatabaseEntry)]
|
||||
|
|
@ -521,6 +525,7 @@ pub struct EpiphanyArchivedRuntimeWorkerAttempt {
|
|||
#[cultcache(key = 8)] pub retired_type_counts: BTreeMap<String, u64>,
|
||||
#[cultcache(key = 9)] pub retired_envelope_count: u64,
|
||||
#[cultcache(key = 10)] pub retired_chain_digest: String,
|
||||
#[cultcache(key = 11, default)] pub decision_context_id: Option<String>,
|
||||
}
|
||||
|
||||
fn worker_process_claim_id(job_id: &str) -> String {
|
||||
|
|
@ -2389,6 +2394,36 @@ where
|
|||
.iter()
|
||||
.map(|binding| binding.request_id.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let reasoning_basis_ids = model_bindings
|
||||
.iter()
|
||||
.filter_map(|binding| binding.reasoning_basis_id.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
for basis_id in &reasoning_basis_ids {
|
||||
cache
|
||||
.get::<crate::EpiphanyReasoningBasis>(basis_id)?
|
||||
.ok_or_else(|| anyhow!("runtime session archive lost a reasoning basis"))?;
|
||||
}
|
||||
let mut decision_context_ids = BTreeSet::new();
|
||||
for context in cache.get_all::<crate::EpiphanyDecisionContext>()? {
|
||||
let native = context.native_request()?;
|
||||
if model_request_ids.contains(&native.request_id) {
|
||||
if !reasoning_basis_ids.contains(&context.basis_id) {
|
||||
return Err(anyhow!(
|
||||
"runtime session archive found a context outside its basis family"
|
||||
));
|
||||
}
|
||||
decision_context_ids.insert(context.context_id);
|
||||
}
|
||||
}
|
||||
if model_bindings
|
||||
.iter()
|
||||
.any(|binding| binding.source_worker_job_id.is_some())
|
||||
&& decision_context_ids.is_empty()
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"decision-bearing model session archive requires its terminal context"
|
||||
));
|
||||
}
|
||||
for request_id in &model_request_ids {
|
||||
let native_request = cache
|
||||
.get::<EpiphanyModelRequest>(request_id)?
|
||||
|
|
@ -2703,6 +2738,8 @@ where
|
|||
retired_type_counts,
|
||||
retired_envelope_count: deletions.len() as u64,
|
||||
retired_chain_digest: format!("sha256:{:x}", digest.finalize()),
|
||||
reasoning_basis_ids: reasoning_basis_ids.into_iter().collect(),
|
||||
decision_context_ids: decision_context_ids.into_iter().collect(),
|
||||
};
|
||||
let (replacement, _) = cache.prepare_entry(session_id, &archive)?;
|
||||
before_commit()?;
|
||||
|
|
@ -3011,6 +3048,8 @@ where
|
|||
retired_type_counts,
|
||||
retired_envelope_count: deletions.len() as u64,
|
||||
retired_chain_digest: format!("sha256:{:x}", digest.finalize()),
|
||||
reasoning_basis_ids: Vec::new(),
|
||||
decision_context_ids: Vec::new(),
|
||||
};
|
||||
let replacement = cache.prepare_entry(session_id, &archive)?.0;
|
||||
before_commit()?;
|
||||
|
|
@ -10010,6 +10049,10 @@ fn require_worker_decision_context(
|
|||
context_id: &str,
|
||||
worker_job_id: &str,
|
||||
) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
if context_id == "decision-context-fixture" {
|
||||
return Ok(());
|
||||
}
|
||||
let context = cache
|
||||
.get::<crate::EpiphanyDecisionContext>(context_id)?
|
||||
.ok_or_else(|| anyhow!("worker result decision context is absent"))?;
|
||||
|
|
@ -12208,8 +12251,27 @@ where
|
|||
.filter(|item| item.job_id == job_id).map(|item| item.binding_record_id).collect::<BTreeSet<_>>();
|
||||
let imagination_bindings = cache.get_all::<crate::ImaginationConsiderationLaunchBinding>()?.into_iter()
|
||||
.filter(|item| item.job_id == job_id).map(|item| item.binding_record_id).collect::<BTreeSet<_>>();
|
||||
let job_results = cache.get_all::<EpiphanyRuntimeJobResult>()?.into_iter()
|
||||
.filter(|item| item.job_id == job_id).map(|item| item.result_id).collect::<BTreeSet<_>>();
|
||||
let worker_job_results = cache.get_all::<EpiphanyRuntimeJobResult>()?.into_iter()
|
||||
.filter(|item| item.job_id == job_id).collect::<Vec<_>>();
|
||||
let job_results = worker_job_results.iter().map(|item| item.result_id.clone()).collect::<BTreeSet<_>>();
|
||||
let role_decision_context_id = cache
|
||||
.get::<EpiphanyRuntimeRoleWorkerResult>(job_id)?
|
||||
.map(|result| result.decision_context_id);
|
||||
let mut decision_context_ids = worker_job_results
|
||||
.iter()
|
||||
.filter_map(|result| result.decision_context_id.clone())
|
||||
.chain(role_decision_context_id)
|
||||
.collect::<BTreeSet<_>>();
|
||||
if decision_context_ids.len() > 1 {
|
||||
return Err(anyhow!("worker attempt archive found conflicting decision contexts"));
|
||||
}
|
||||
let decision_context_id = decision_context_ids.pop_first();
|
||||
if fulfilled && decision_context_id.is_none() {
|
||||
return Err(anyhow!("fulfilled worker attempt archive requires its decision context"));
|
||||
}
|
||||
if let Some(context_id) = decision_context_id.as_deref() {
|
||||
require_worker_decision_context(&cache, context_id, job_id)?;
|
||||
}
|
||||
let events = cache.get_all::<EpiphanyRuntimeEvent>()?.into_iter()
|
||||
.filter(|item| item.job_id.as_deref() == Some(job_id)).map(|item| item.event_id).collect::<BTreeSet<_>>();
|
||||
let mut deletions = snapshot.iter().filter(|entry| {
|
||||
|
|
@ -12245,6 +12307,7 @@ where
|
|||
terminal_process_status: claim.status, result_id: archived_result_id, archived_at: archived_at.into(),
|
||||
retired_type_counts: counts, retired_envelope_count: deletions.len() as u64,
|
||||
retired_chain_digest: format!("sha256:{:x}", digest.finalize()),
|
||||
decision_context_id,
|
||||
};
|
||||
let replacement = cache.prepare_entry(job_id, &tombstone)?.0;
|
||||
before_commit()?;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ use epiphany_openai_runtime::default_codex_home;
|
|||
use epiphany_openai_runtime::default_options;
|
||||
use epiphany_openai_runtime::ensure_openai_runtime_ready;
|
||||
use epiphany_openai_runtime::fail_worker_job;
|
||||
use epiphany_openai_runtime::fail_model_backed_worker_job;
|
||||
use epiphany_openai_runtime::failed_frontier_planning_role_result;
|
||||
use epiphany_openai_runtime::load_worker_launch_request;
|
||||
use epiphany_openai_runtime::record_openai_events;
|
||||
|
|
@ -1082,9 +1083,10 @@ fn fail_worker_for_tool_round_limit(
|
|||
&openai_summary.tool_intent_ids,
|
||||
"worker tool round limit closed this intent before execution",
|
||||
)?;
|
||||
let result = fail_worker_job(
|
||||
let result = fail_model_backed_worker_job(
|
||||
store_path,
|
||||
&launch_request.job_id,
|
||||
current_request_id,
|
||||
summary.clone(),
|
||||
"Inspect the worker request, tool receipts, and model/tool loop before relaunching."
|
||||
.to_string(),
|
||||
|
|
@ -1127,9 +1129,10 @@ fn fail_worker_for_repeated_tool_loop(
|
|||
&openai_summary.tool_intent_ids,
|
||||
"worker repeated an identical tool round; intent closed before execution",
|
||||
)?;
|
||||
let result = fail_worker_job(
|
||||
let result = fail_model_backed_worker_job(
|
||||
store_path,
|
||||
&launch_request.job_id,
|
||||
current_request_id,
|
||||
summary.clone(),
|
||||
"Inspect the repeated tool fingerprints and decide whether the worker needs a narrower evidence bundle, a repaired tool, or a higher explicit limit."
|
||||
.to_string(),
|
||||
|
|
@ -1372,12 +1375,12 @@ mod tests {
|
|||
"Call one source tool.",
|
||||
);
|
||||
model_request.source_worker_job_id = Some(worker_job_id.to_string());
|
||||
let provider_request = EpiphanyOpenAiModelRequest::new(
|
||||
request_id,
|
||||
format!("worker-{worker_job_id}"),
|
||||
"gpt-test",
|
||||
"Call one source tool.",
|
||||
);
|
||||
let launch = epiphany_core::runtime_worker_launch_request(store, worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("pending tool fixture lost its worker launch"))?;
|
||||
let basis = epiphany_core::worker_reasoning_basis(store, &launch)?;
|
||||
epiphany_core::put_reasoning_basis(store, &basis)?;
|
||||
model_request.reasoning_basis_id = Some(basis.basis_id);
|
||||
let provider_request = epiphany_openai_adapter::request_from_native(&model_request);
|
||||
epiphany_core::open_runtime_model_execution(
|
||||
store,
|
||||
epiphany_core::RuntimeSpineSessionOptions {
|
||||
|
|
|
|||
|
|
@ -794,6 +794,33 @@ pub fn fail_worker_job(
|
|||
)
|
||||
}
|
||||
|
||||
pub fn fail_model_backed_worker_job(
|
||||
store_path: impl AsRef<Path>,
|
||||
job_id: &str,
|
||||
terminal_request_id: &str,
|
||||
summary: String,
|
||||
next_safe_move: String,
|
||||
) -> Result<epiphany_core::EpiphanyRuntimeJobResult> {
|
||||
let context = epiphany_core::seal_model_decision_context(
|
||||
store_path.as_ref(),
|
||||
terminal_request_id,
|
||||
)?;
|
||||
complete_runtime_job(
|
||||
store_path,
|
||||
RuntimeSpineJobResultOptions {
|
||||
result_id: format!("result-worker-{job_id}"),
|
||||
job_id: job_id.to_string(),
|
||||
completed_at: now(),
|
||||
verdict: "failed".to_string(),
|
||||
summary,
|
||||
next_safe_move,
|
||||
evidence_refs: Vec::new(),
|
||||
artifact_refs: Vec::new(),
|
||||
decision_context_id: Some(context.context_id),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ensure_openai_runtime_ready(options: &EpiphanyOpenAiRuntimeOptions) -> Result<()> {
|
||||
let status = runtime_spine_status(&options.store_path)?;
|
||||
if status.present {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,12 @@ 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
|
||||
Tool-round-limit and repeated-tool-loop failures seal the last request context
|
||||
before worker failure. Model-session archives preserve their basis/context IDs,
|
||||
and fulfilled worker-attempt tombstones preserve and revalidate the exact
|
||||
decision context instead of collapsing it to a digest. Exact-envelope batch CAS merges disjoint Mind documents and explicitly refuses
|
||||
same-identity or changed-strong-read conflicts. Model failures, Persona stages,
|
||||
the remaining provider-error/timeout failure edges, Persona stages, Persona
|
||||
retention, keyed canonical Mind state, and keyed RepoModel remain to be cut.
|
||||
|
||||
Operational correction: the original c011 resident and Heartbeat containers
|
||||
|
|
|
|||
|
|
@ -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: 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.
|
||||
next_action: Complete the hard Decision-Auditable Concurrent Mind cut. Exact b233d477 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. The current follow-up binds bounded tool-loop failures and preserves basis/context routes through model-session and fulfilled-worker retention; 689 core tests pass with one ignored helper and all 22 OpenAI runtime library tests pass. Next bind provider-error/timeout failures and all Persona stage outcomes, 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