Make agent admission refusal replayable
This commit is contained in:
parent
ec9f9850f9
commit
e046a4d190
10 changed files with 819 additions and 94 deletions
|
|
@ -71,10 +71,19 @@ fn repo_frontier_item_output_schema() -> serde_json::Value {
|
|||
"migration_body": {"type": "string", "minLength": 1},
|
||||
"question": {"type": "string", "minLength": 1},
|
||||
"gap": {"type": "string", "minLength": 1},
|
||||
"target_claim_ids": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}},
|
||||
"target_claim_ids": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"description": "RepoModel node identities whose claims this frontier item changes or resolves.",
|
||||
"items": {"type": "string", "minLength": 1}
|
||||
},
|
||||
"source_scope": {"type": "array", "items": {"type": "string"}},
|
||||
"recommended_next_organ": {"type": "string", "minLength": 1},
|
||||
"dependency_item_ids": {"type": "array", "items": {"type": "string"}},
|
||||
"dependency_item_ids": {
|
||||
"type": "array",
|
||||
"description": "RepoModel frontier-item identities that must resolve first. Never put node or claim identities here.",
|
||||
"items": {"type": "string", "minLength": 1}
|
||||
},
|
||||
"status": {"type": "string", "enum": ["proposed", "active", "blocked", "resolved", "retired", "superseded"]},
|
||||
"evidence_refs": {"type": "array", "items": {"type": "string"}}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -817,14 +817,14 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
steps.push(step);
|
||||
break;
|
||||
}
|
||||
let accepted = epiphany_core::accept_frontier_verification_result(
|
||||
let admission = epiphany_core::accept_frontier_verification_result(
|
||||
&runtime_store,
|
||||
&job_id,
|
||||
&now(),
|
||||
)?;
|
||||
push_event(
|
||||
&mut step,
|
||||
json!({"type": "frontierVerificationAccept", "roleId": role_id, "commit": accepted}),
|
||||
json!({"type": "frontierVerificationAdmission", "roleId": role_id, "outcome": admission}),
|
||||
);
|
||||
final_status = collect_coordinator_status(&runtime_store, &thread_id)?;
|
||||
append_operator_step_jsonl(&steps_path, &step)?;
|
||||
|
|
@ -854,14 +854,14 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
steps.push(step);
|
||||
break;
|
||||
}
|
||||
let accepted = epiphany_core::accept_proposal_modeling_result(
|
||||
let admission = epiphany_core::accept_proposal_modeling_result(
|
||||
&runtime_store,
|
||||
&job_id,
|
||||
&now(),
|
||||
)?;
|
||||
push_event(
|
||||
&mut step,
|
||||
json!({"type": "proposalModelingAccept", "roleId": role_id, "commit": accepted}),
|
||||
json!({"type": "proposalModelingAdmission", "roleId": role_id, "outcome": admission}),
|
||||
);
|
||||
final_status = collect_coordinator_status(&runtime_store, &thread_id)?;
|
||||
append_operator_step_jsonl(&steps_path, &step)?;
|
||||
|
|
@ -891,14 +891,14 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
steps.push(step);
|
||||
break;
|
||||
}
|
||||
let accepted = epiphany_core::accept_body_modeling_result(
|
||||
let admission = epiphany_core::accept_body_modeling_result(
|
||||
&runtime_store,
|
||||
&job_id,
|
||||
&now(),
|
||||
)?;
|
||||
push_event(
|
||||
&mut step,
|
||||
json!({"type": "bodyModelingAccept", "roleId": role_id, "commit": accepted}),
|
||||
json!({"type": "bodyModelingAdmission", "roleId": role_id, "outcome": admission}),
|
||||
);
|
||||
final_status = collect_coordinator_status(&runtime_store, &thread_id)?;
|
||||
append_operator_step_jsonl(&steps_path, &step)?;
|
||||
|
|
@ -932,14 +932,14 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
steps.push(step);
|
||||
break;
|
||||
}
|
||||
let accepted = epiphany_core::accept_frontier_verdict_modeling_result(
|
||||
let admission = epiphany_core::accept_frontier_verdict_modeling_result(
|
||||
&runtime_store,
|
||||
&job_id,
|
||||
&now(),
|
||||
)?;
|
||||
push_event(
|
||||
&mut step,
|
||||
json!({"type": "frontierVerdictModelingAccept", "roleId": role_id, "commit": accepted}),
|
||||
json!({"type": "frontierVerdictModelingAdmission", "roleId": role_id, "outcome": admission}),
|
||||
);
|
||||
final_status = collect_coordinator_status(&runtime_store, &thread_id)?;
|
||||
append_operator_step_jsonl(&steps_path, &step)?;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ use anyhow::Context;
|
|||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use chrono::SecondsFormat;
|
||||
use epiphany_core::EpiphanyCoordinatorRunReceipt;
|
||||
use epiphany_core::EpiphanyRuntimeEvent;
|
||||
use epiphany_core::EpiphanyRuntimeJob;
|
||||
use epiphany_core::EpiphanyRuntimeJobResult;
|
||||
use epiphany_core::EpiphanyRuntimeRoleWorkerResult;
|
||||
use epiphany_core::EpiphanyRuntimeSession;
|
||||
use epiphany_core::RuntimeSpineEventOptions;
|
||||
use epiphany_core::RuntimeSpineInitOptions;
|
||||
|
|
@ -184,6 +186,52 @@ fn main() -> Result<()> {
|
|||
);
|
||||
}
|
||||
}
|
||||
Command::ListCoordinatorRuns => {
|
||||
let mut cache = runtime_spine_cache(&args.store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let mut receipts = cache.get_all::<EpiphanyCoordinatorRunReceipt>()?;
|
||||
receipts.sort_by(|left, right| {
|
||||
left.created_at
|
||||
.cmp(&right.created_at)
|
||||
.then_with(|| left.receipt_id.cmp(&right.receipt_id))
|
||||
});
|
||||
println!("coordinator runs");
|
||||
for receipt in receipts {
|
||||
println!(
|
||||
"{}\t{}\t{}\t{}\t{}\t{}",
|
||||
receipt.created_at,
|
||||
receipt.receipt_id,
|
||||
receipt.thread_id,
|
||||
receipt.status,
|
||||
receipt.final_action,
|
||||
receipt
|
||||
.final_reason
|
||||
.as_deref()
|
||||
.unwrap_or("no-reason")
|
||||
.replace(['\r', '\n', '\t'], " ")
|
||||
);
|
||||
}
|
||||
}
|
||||
Command::ListRepoModelProposals => {
|
||||
let mut cache = runtime_spine_cache(&args.store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let mut results = cache.get_all::<EpiphanyRuntimeRoleWorkerResult>()?;
|
||||
results.sort_by(|left, right| left.job_id.cmp(&right.job_id));
|
||||
println!("RepoModel mutation proposals");
|
||||
for result in results {
|
||||
let Some(proposal) = result.repo_model_mutation_proposal()? else {
|
||||
continue;
|
||||
};
|
||||
let operations = proposal.operations()?;
|
||||
println!(
|
||||
"{}\t{}\t{}\t{}",
|
||||
result.job_id,
|
||||
proposal.proposal_id,
|
||||
result.decision_context_id,
|
||||
serde_json::to_string(&operations)?
|
||||
);
|
||||
}
|
||||
}
|
||||
Command::OpenSession {
|
||||
session_id,
|
||||
objective,
|
||||
|
|
@ -334,6 +382,8 @@ enum Command {
|
|||
ListSessions,
|
||||
ListModelRequests,
|
||||
ListEvents,
|
||||
ListCoordinatorRuns,
|
||||
ListRepoModelProposals,
|
||||
OpenSession {
|
||||
session_id: String,
|
||||
objective: String,
|
||||
|
|
@ -425,6 +475,8 @@ fn parse_command(mut args: Vec<String>) -> Result<Command> {
|
|||
"list-sessions" => Ok(Command::ListSessions),
|
||||
"list-model-requests" => Ok(Command::ListModelRequests),
|
||||
"list-events" => Ok(Command::ListEvents),
|
||||
"list-coordinator-runs" => Ok(Command::ListCoordinatorRuns),
|
||||
"list-repo-model-proposals" => Ok(Command::ListRepoModelProposals),
|
||||
"open-session" => {
|
||||
let mut session_id = format!("session-{}", Uuid::new_v4());
|
||||
let mut objective = String::new();
|
||||
|
|
@ -709,7 +761,7 @@ fn now() -> String {
|
|||
}
|
||||
|
||||
fn usage() -> &'static str {
|
||||
"usage: epiphany-runtime-spine [--store path] <init|status|list-jobs|list-sessions|list-model-requests|list-events|open-session|close-session|repair-root-session|open-job|complete-job|record-event|hello-frame|schema-catalog>"
|
||||
"usage: epiphany-runtime-spine [--store path] <init|status|list-jobs|list-sessions|list-model-requests|list-events|list-coordinator-runs|list-repo-model-proposals|open-session|close-session|repair-root-session|open-job|complete-job|record-event|hello-frame|schema-catalog>"
|
||||
}
|
||||
|
||||
fn model_input_item_chars(item: &EpiphanyModelInputItem) -> usize {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@ use sha2::{Digest, Sha256};
|
|||
|
||||
use crate::{EpiphanyMindDocumentVersion, runtime_spine_cache};
|
||||
|
||||
pub const MIND_SCHEMA_EPOCH: &str = "epiphany.mind.epoch.v2";
|
||||
pub const MIND_SCHEMA_EPOCH: &str = "epiphany.mind.epoch.v3";
|
||||
pub const MIND_OBJECTIVE_KEY: &str = "objective";
|
||||
pub const MIND_FOCUS_KEY: &str = "focus";
|
||||
pub const MIND_MODE_KEY: &str = "mode";
|
||||
|
|
@ -354,6 +354,7 @@ pub struct EpiphanyMindView {
|
|||
pub verification_audits: Vec<EpiphanyMindVerificationAuditDocument>,
|
||||
pub reorientation_decisions: Vec<crate::EpiphanyMindReorientationDecisionDocument>,
|
||||
pub reorientation_failures: Vec<crate::EpiphanyMindReorientationPassFailureDocument>,
|
||||
pub agent_pass_admission_refusals: Vec<crate::EpiphanyAgentPassAdmissionRefusal>,
|
||||
pub investigation_checkpoint: Option<EpiphanyInvestigationCheckpoint>,
|
||||
pub mode: Option<EpiphanyModeState>,
|
||||
pub planning: EpiphanyPlanningState,
|
||||
|
|
@ -450,6 +451,11 @@ pub(crate) fn validate_mind_write_envelope(envelope: &CultCacheEnvelope) -> Resu
|
|||
)?;
|
||||
value.validate(&work)?;
|
||||
value.work_id
|
||||
} else if envelope.r#type == crate::EpiphanyAgentPassAdmissionRefusal::TYPE {
|
||||
let value: crate::EpiphanyAgentPassAdmissionRefusal =
|
||||
rmp_serde::from_slice(&envelope.payload)?;
|
||||
value.validate()?;
|
||||
value.refusal_id
|
||||
} else if envelope.r#type == EpiphanyMindIdentity::TYPE {
|
||||
let value: EpiphanyMindIdentity = rmp_serde::from_slice(&envelope.payload)?;
|
||||
if value.schema_epoch != MIND_SCHEMA_EPOCH || value.runtime_id.trim().is_empty() {
|
||||
|
|
@ -587,6 +593,11 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
|
|||
cache.get_all::<crate::EpiphanyMindReorientationDecisionDocument>()?;
|
||||
let mut reorientation_failures =
|
||||
cache.get_all::<crate::EpiphanyMindReorientationPassFailureDocument>()?;
|
||||
let mut agent_pass_admission_refusals =
|
||||
cache.get_all::<crate::EpiphanyAgentPassAdmissionRefusal>()?;
|
||||
for refusal in &agent_pass_admission_refusals {
|
||||
refusal.validate()?;
|
||||
}
|
||||
let checkpoints =
|
||||
values::<EpiphanyMindInvestigationCheckpointDocument, _>(&cache, |value| value.value)?;
|
||||
let mut captures =
|
||||
|
|
@ -630,6 +641,7 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
|
|||
verification_audits.sort_by(|left, right| left.audit_id.cmp(&right.audit_id));
|
||||
reorientation_decisions.sort_by(|left, right| left.decision_id.cmp(&right.decision_id));
|
||||
reorientation_failures.sort_by(|left, right| left.failure_id.cmp(&right.failure_id));
|
||||
agent_pass_admission_refusals.sort_by(|left, right| left.refusal_id.cmp(&right.refusal_id));
|
||||
captures.sort_by(|left, right| left.id.cmp(&right.id));
|
||||
backlog_items.sort_by(|left, right| left.id.cmp(&right.id));
|
||||
roadmap_streams.sort_by(|left, right| left.id.cmp(&right.id));
|
||||
|
|
@ -674,6 +686,7 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
|
|||
verification_audits,
|
||||
reorientation_decisions,
|
||||
reorientation_failures,
|
||||
agent_pass_admission_refusals,
|
||||
investigation_checkpoint,
|
||||
mode,
|
||||
planning: EpiphanyPlanningState {
|
||||
|
|
@ -714,6 +727,7 @@ fn canonical_mind_versions(
|
|||
| EpiphanyMindVerificationAuditDocument::TYPE
|
||||
| crate::EpiphanyMindReorientationDecisionDocument::TYPE
|
||||
| crate::EpiphanyMindReorientationPassFailureDocument::TYPE
|
||||
| crate::EpiphanyAgentPassAdmissionRefusal::TYPE
|
||||
| EpiphanyMindInvestigationCheckpointDocument::TYPE
|
||||
| EpiphanyMindPlanningCaptureDocument::TYPE
|
||||
| EpiphanyMindBacklogItemDocument::TYPE
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ pub struct EpiphanyMindPromptProjection {
|
|||
pub verification_audits: Vec<crate::EpiphanyMindVerificationAuditDocument>,
|
||||
pub reorientation_decisions: Vec<crate::EpiphanyMindReorientationDecisionDocument>,
|
||||
pub reorientation_failures: Vec<crate::EpiphanyMindReorientationPassFailureDocument>,
|
||||
pub agent_pass_admission_refusals: Vec<crate::EpiphanyAgentPassAdmissionRefusal>,
|
||||
pub investigation_checkpoint: Option<epiphany_state_model::EpiphanyInvestigationCheckpoint>,
|
||||
pub mode: Option<epiphany_state_model::EpiphanyModeState>,
|
||||
pub planning: epiphany_state_model::EpiphanyPlanningState,
|
||||
|
|
@ -141,6 +142,7 @@ impl From<crate::EpiphanyMindView> for EpiphanyMindPromptProjection {
|
|||
verification_audits: value.verification_audits,
|
||||
reorientation_decisions: value.reorientation_decisions,
|
||||
reorientation_failures: value.reorientation_failures,
|
||||
agent_pass_admission_refusals: value.agent_pass_admission_refusals,
|
||||
investigation_checkpoint: value.investigation_checkpoint,
|
||||
mode: value.mode,
|
||||
planning: value.planning,
|
||||
|
|
@ -2023,6 +2025,7 @@ mod tests {
|
|||
verification_audits: Vec::new(),
|
||||
reorientation_decisions: Vec::new(),
|
||||
reorientation_failures: Vec::new(),
|
||||
agent_pass_admission_refusals: Vec::new(),
|
||||
investigation_checkpoint: None,
|
||||
mode: None,
|
||||
planning: Default::default(),
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ pub const TOOL_INVOCATION_RECEIPT_TYPE: &str = "epiphany.tool_invocation_receipt
|
|||
pub const RUNTIME_IDENTITY_KEY: &str = "self";
|
||||
pub const RUNTIME_SWARM_BINDING_KEY: &str = "runtime-swarm-binding";
|
||||
pub const RUNTIME_SWARM_BINDING_SCHEMA_VERSION: &str = "epiphany.runtime.swarm_binding.v0";
|
||||
pub const RUNTIME_SPINE_SCHEMA_VERSION: &str = "epiphany.runtime_spine.v3";
|
||||
pub const RUNTIME_SPINE_SCHEMA_VERSION: &str = "epiphany.runtime_spine.v4";
|
||||
pub const EPIPHANY_RUNTIME_ROOT_SESSION_ID: &str = "epiphany-main";
|
||||
pub const RUNTIME_MODEL_EXECUTION_BINDING_SCHEMA_VERSION: &str =
|
||||
"epiphany.runtime.model_execution_binding.v0";
|
||||
|
|
@ -5870,6 +5870,13 @@ pub(crate) fn validate_proposal_modeling_worker_fulfillment(
|
|||
&proposal.evidence_refs,
|
||||
&proposal.public_source_refs,
|
||||
)?;
|
||||
let attempt_ordinal =
|
||||
crate::current_work::proposal_modeling_attempt_ordinal(request_id, &result.job_id)?;
|
||||
let prior_admission_refusals = crate::current_work::proposal_modeling_prior_admission_refusals(
|
||||
cache,
|
||||
request_id,
|
||||
attempt_ordinal,
|
||||
)?;
|
||||
let projection_matches_authenticated_request = projection.is_some_and(|projection| {
|
||||
let expected = crate::RepoFrontierProposalModelingContextProjection {
|
||||
schema_version: crate::REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_SCHEMA_VERSION.into(),
|
||||
|
|
@ -5894,6 +5901,7 @@ pub(crate) fn validate_proposal_modeling_worker_fulfillment(
|
|||
private_state_included: proposal.private_state_included,
|
||||
model_projection_digest: projection.model_projection_digest.clone(),
|
||||
model_source_documents: projection.model_source_documents.clone(),
|
||||
prior_admission_refusals: prior_admission_refusals.clone(),
|
||||
};
|
||||
projection == &expected
|
||||
});
|
||||
|
|
@ -6046,11 +6054,28 @@ pub fn runtime_typed_request_fulfillment(
|
|||
validate_non_empty(request_id, "typed fulfillment request id")?;
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let admission_refusals = cache
|
||||
.get_all::<crate::EpiphanyAgentPassAdmissionRefusal>()?
|
||||
.into_iter()
|
||||
.map(|refusal| {
|
||||
refusal.validate()?;
|
||||
Ok(refusal)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let archived_matches = cache
|
||||
.get_all::<EpiphanyArchivedRuntimeWorkerAttempt>()?
|
||||
.into_iter()
|
||||
.filter(|attempt| {
|
||||
attempt.request_id == request_id && attempt.fulfilled_result_id().is_some()
|
||||
attempt.request_id == request_id
|
||||
&& attempt.fulfilled_result_id().is_some()
|
||||
&& !admission_refusals.iter().any(|refusal| {
|
||||
refusal.pass_family.request_kind() == request.kind()
|
||||
&& refusal.request_id == request_id
|
||||
&& refusal.job_id == attempt.job_id
|
||||
&& attempt.fulfilled_result_id() == Some(refusal.result_id.as_str())
|
||||
&& attempt.decision_context_id()
|
||||
== Some(refusal.decision_context_id.as_str())
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if archived_matches.len() > 1 {
|
||||
|
|
@ -6107,7 +6132,16 @@ pub fn runtime_typed_request_fulfillment(
|
|||
let matches = cache
|
||||
.get_all::<EpiphanyRuntimeRoleWorkerResult>()?
|
||||
.into_iter()
|
||||
.filter(|result| request.matches_result(result))
|
||||
.filter(|result| {
|
||||
request.matches_result(result)
|
||||
&& !admission_refusals.iter().any(|refusal| {
|
||||
refusal.pass_family.request_kind() == request.kind()
|
||||
&& refusal.request_id == request_id
|
||||
&& refusal.job_id == result.job_id
|
||||
&& refusal.result_id == result.result_id
|
||||
&& refusal.decision_context_id == result.decision_context_id
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if matches.is_empty() {
|
||||
return Ok(None);
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ mod tests {
|
|||
verification_audits: Vec::new(),
|
||||
reorientation_decisions: Vec::new(),
|
||||
reorientation_failures: Vec::new(),
|
||||
agent_pass_admission_refusals: Vec::new(),
|
||||
investigation_checkpoint: None,
|
||||
mode: None,
|
||||
planning: EpiphanyPlanningState {
|
||||
|
|
|
|||
|
|
@ -429,6 +429,7 @@ mod tests {
|
|||
verification_audits: Vec::new(),
|
||||
reorientation_decisions: Vec::new(),
|
||||
reorientation_failures: Vec::new(),
|
||||
agent_pass_admission_refusals: Vec::new(),
|
||||
investigation_checkpoint: None,
|
||||
mode: None,
|
||||
planning: EpiphanyPlanningState::default(),
|
||||
|
|
@ -449,10 +450,8 @@ mod tests {
|
|||
Some(true)
|
||||
);
|
||||
assert_eq!(scene.invariant_status_counts[0].count, 2);
|
||||
assert!(
|
||||
scene
|
||||
.available_actions
|
||||
.contains(&EpiphanySceneAction::ReorientResult)
|
||||
);
|
||||
assert!(scene
|
||||
.available_actions
|
||||
.contains(&EpiphanySceneAction::ReorientResult));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ use serde::Serialize;
|
|||
pub const ROLE_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.role_result.v4";
|
||||
pub const REORIENT_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.reorient_result.v0";
|
||||
pub const REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_SCHEMA_VERSION: &str =
|
||||
"epiphany.worker.repo_frontier_proposal_modeling_context.v1";
|
||||
"epiphany.worker.repo_frontier_proposal_modeling_context.v2";
|
||||
pub const REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_CONTRACT: &str =
|
||||
"epiphany.repo_frontier_proposal_modeling_context.v1";
|
||||
"epiphany.repo_frontier_proposal_modeling_context.v2";
|
||||
pub const REPO_FRONTIER_PLANNING_CONTEXT_SCHEMA_VERSION: &str =
|
||||
"epiphany.worker.repo_frontier_planning_context.v2";
|
||||
pub const REPO_FRONTIER_PLANNING_CONTEXT_CONTRACT: &str =
|
||||
|
|
@ -209,6 +209,7 @@ pub struct RepoFrontierProposalModelingContextProjection {
|
|||
pub private_state_included: bool,
|
||||
pub model_projection_digest: String,
|
||||
pub model_source_documents: Vec<crate::EpiphanyMindDocumentVersion>,
|
||||
pub prior_admission_refusals: Vec<crate::EpiphanyAgentPassAdmissionRefusal>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
|
|||
Reference in a new issue