use aidens_agency_kit::{
AgencyPolicyEngineV1, AgencyPolicyInputV1, AgencyPolicyOutcomeV1, AgencyPolicyReportV1,
NudgeLedgerV1,
};
use aidens_boundary_kit::{parse_json_boundary, BoundaryRepairPolicyV1};
use aidens_budget_kit::BudgetV1;
use aidens_contracts::{
display_only_unstable_id, AgentMemoryModeV1, AgentProviderModeV1, AgentSpecSupportLabelV1,
AgentSpecV1, AgentVerificationCheckV1, AiDENsRunBudgetDeadlineV1, AiDENsRunBundleV3,
AiDENsRunEventLogDigestV1, AiDENsRunFailureTaxonomyV1, AiDENsRunReplayNormalizationV1,
AiDENsRunSupportTierEvidenceV1, AidensRunContextV1, ArtifactId, BoundaryRepairReportV1,
BudgetExhaustionReportDraftV1, BudgetExhaustionReportV1, DegradationEventV1, DisplayDigestV1,
JsonBoundaryRepairDisplayReportV1, ProjectionDigestV1, ProviderRouteKindV1,
ProviderRouteReportV1, QueryWideningReportV1, ReportLevelV1, RunReportV1, RuntimeViewRequestV1,
StackAttemptId, StackTrialId, StopRuleReportV1, StopRuleV1, ToolCallRequestV1,
ToolCallResultV1, ToolCallSourceV1, ToolExposureSetV1, ToolInvocationReportV1,
TurnExecutionPlanV1, TurnFinalStateV1, TurnModeV1, TurnReportV1, ViewDisclosureReportV1,
};
use aidens_governance_kit::{
canonical_stack as governance_stack, CanonicalGovernanceAdapter, GovernanceContext,
};
use aidens_kernel_kit::CanonicalKernelAdapter;
use aidens_memory_kit::{
canonical_stack, memory_config_for_root, runtime_config_for_namespace, CanonicalMemoryAdapter,
MemoryGroundingEvidenceV1,
};
use aidens_permit_kit::PermitPolicyV1;
use aidens_provider_kit::{
build_provider, route_receipt, AiDENsChatMessageV1, AiDENsCompletionRequestV1,
AiDENsCompletionResponseV1, AiDENsProvider, ProviderSpecV1,
};
use aidens_receipts::{CanonicalEventLog, CanonicalEventLogConfig, CanonicalEventLogEntry};
use aidens_tool_kit::{
safe_coding_registry_for_current_dir, ToolDispatcher, ToolExposurePolicyV1,
ToolInvocationError, ToolRegistryV1,
};
use anyhow::anyhow;
use std::collections::BTreeSet;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Instant;
use std::time::{SystemTime, UNIX_EPOCH};
mod execution;
mod finalization;
mod provider_tool;
mod receipts;
mod replay;
use execution::*;
use finalization::*;
use provider_tool::*;
pub use receipts::*;
use replay::*;
pub struct PlanActVerifyLoopV1 {
app_id: String,
spec: AgentSpecV1,
tools: ToolRegistryV1,
permit_policy: PermitPolicyV1,
provider_mock_response: Option<String>,
provider_model: Option<String>,
provider_base_url: Option<String>,
provider_api_key: Option<String>,
sandbox_root: Option<String>,
canonical_receipt_log_config: Option<CanonicalEventLogConfig>,
budget_retries: u32,
run_reports: RunReportLedger,
receipt_level: ReportLevelV1,
agency_policy: AgencyPolicyEngineV1,
agency_nudges: Arc<Mutex<NudgeLedgerV1>>,
memory: Option<std::sync::Arc<aidens_memory_kit::CanonicalMemoryAdapter>>,
governance: Option<aidens_governance_kit::GovernanceContext>,
kernel: Option<aidens_kernel_kit::CanonicalKernelAdapter>,
}
#[derive(Debug, Clone)]
pub struct AiDENsRunInput {
pub prompt: String,
}
impl AiDENsRunInput {
pub fn new(prompt: impl Into<String>) -> Self {
Self {
prompt: prompt.into(),
}
}
}
impl PlanActVerifyLoopV1 {
pub fn new(spec: AgentSpecV1) -> Self {
Self {
app_id: "aidens-plan-act-loop".into(),
spec,
tools: safe_coding_registry_for_current_dir(),
permit_policy: PermitPolicyV1::default(),
provider_mock_response: None,
provider_model: None,
provider_base_url: None,
provider_api_key: None,
sandbox_root: None,
canonical_receipt_log_config: Some(default_plan_act_verify_receipt_log_config()),
budget_retries: 2,
run_reports: RunReportLedger::default(),
receipt_level: ReportLevelV1::Full,
agency_policy: AgencyPolicyEngineV1::default(),
agency_nudges: Arc::new(Mutex::new(NudgeLedgerV1::default())),
memory: None,
governance: None,
kernel: None,
}
}
pub fn app_id(mut self, app_id: impl Into<String>) -> Self {
self.app_id = app_id.into();
self
}
pub fn tools(mut self, tools: ToolRegistryV1) -> Self {
self.tools = tools;
self
}
pub fn permit_policy(mut self, permit_policy: PermitPolicyV1) -> Self {
self.permit_policy = permit_policy;
self
}
pub fn provider_mock_response(mut self, response: impl Into<String>) -> Self {
self.provider_mock_response = Some(response.into());
self
}
pub fn provider_model(mut self, model: impl Into<String>) -> Self {
self.provider_model = Some(model.into());
self
}
pub fn provider_base_url(mut self, base_url: impl Into<String>) -> Self {
self.provider_base_url = Some(base_url.into());
self
}
pub fn api_key(mut self, key: impl Into<String>) -> Self {
self.provider_api_key = Some(key.into());
self
}
pub fn sandbox_root(mut self, sandbox_root: impl Into<String>) -> Self {
self.sandbox_root = Some(sandbox_root.into());
self
}
pub fn canonical_receipt_log_config(mut self, config: CanonicalEventLogConfig) -> Self {
self.canonical_receipt_log_config = Some(config);
self
}
pub fn with_agency_policy(mut self, policy: AgencyPolicyEngineV1) -> Self {
self.agency_policy = policy;
self
}
pub fn with_run_reports(mut self, reports: RunReportLedger) -> Self {
self.run_reports = reports;
self
}
pub fn with_memory(
mut self,
memory: std::sync::Arc<aidens_memory_kit::CanonicalMemoryAdapter>,
) -> Self {
self.memory = Some(memory);
self
}
pub fn with_governance(mut self, governance: aidens_governance_kit::GovernanceContext) -> Self {
self.governance = Some(governance);
self
}
pub fn with_kernel_reasoning(
mut self,
kernel: aidens_kernel_kit::CanonicalKernelAdapter,
) -> Self {
self.kernel = Some(kernel);
self
}
pub fn has_memory(&self) -> bool {
self.memory.is_some()
}
pub fn has_governance(&self) -> bool {
self.governance.is_some()
}
pub fn has_kernel(&self) -> bool {
self.kernel.is_some()
}
pub fn max_retries(mut self, retries: u32) -> Self {
self.budget_retries = retries;
self
}
pub async fn execute(
&self,
input: impl Into<String>,
) -> anyhow::Result<PlanActVerifyLoopV1Output> {
let prompt = input.into();
if let Err(reason_codes) = self.spec.validate() {
let mut output = PlanActVerifyLoopV1Output::abstention(
&self.spec,
1,
"agent-spec-validation-failed",
);
output.verification_receipts.push(VerificationReceiptV1 {
receipt_id: display_only_unstable_id("agent-verification"),
step: 1,
check: "agent-spec".into(),
passed: false,
reason_codes,
});
return Ok(output);
}
let mut memory_grounding_receipts = Vec::new();
if self.spec.memory_policy.enabled
&& self.spec.memory_policy.mode == AgentMemoryModeV1::CanonicalSeam
{
match run_canonical_memory_grounding(&self.spec, &prompt).await {
Ok(receipts) => {
memory_grounding_receipts = receipts;
}
Err(error) => {
let mut output = PlanActVerifyLoopV1Output::abstention(
&self.spec,
1,
"memory-grounding-failed",
);
output.memory_grounding_receipts = vec![error.to_string()];
output.abstention_receipt = Some(AbstentionReceiptV1 {
receipt_id: display_only_unstable_id("agent-abstention"),
step: 1,
reason_code: "memory-grounding-failed".into(),
blocked_action: "memory-grounding-query".into(),
evidence: vec![error.to_string()],
required_permits: Vec::new(),
can_resume: true,
support_impact: "degraded".into(),
});
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step: 1,
outcome: "abstained-memory-grounding".into(),
final_state: "memory-grounding-error".into(),
blocked: true,
support_label: self.spec.support_label.to_string(),
reason_codes: vec!["memory-grounding-failed".into()],
});
output.repair_plan = Some(RepairPlanDisplayReceiptV1 {
repair_id: display_only_unstable_id("agent-repair"),
source_run_id: None,
failure_kind: "memory-grounding".into(),
candidate_repair_actions: vec![
"re-check memory seam fixture availability".into(),
"adjust query for grounded scope".into(),
],
required_verification: vec!["memory-grounding".into()],
required_permits: Vec::new(),
risk_level: "moderate".into(),
canonical_owner: Some("knowledge-runtime".into()),
});
return Ok(output);
}
}
}
let mapped_tools = map_agent_tools_to_ids(&self.spec.tool_policy.allowed_tools);
if !mapped_tools.unsupported.is_empty() {
let mut output = PlanActVerifyLoopV1Output::abstention(
&self.spec,
1,
"tool-policy-contains-unsupported-alias",
);
output.memory_grounding_receipts = memory_grounding_receipts;
output.verification_receipts.push(VerificationReceiptV1 {
receipt_id: display_only_unstable_id("agent-verification"),
step: 1,
check: "tool-policy".into(),
passed: false,
reason_codes: mapped_tools
.unsupported
.iter()
.map(|alias| format!("unsupported-tool-alias:{alias}"))
.collect(),
});
output.repair_plan = Some(RepairPlanDisplayReceiptV1 {
repair_id: display_only_unstable_id("agent-repair"),
source_run_id: None,
failure_kind: "unsupported-tool-alias".into(),
candidate_repair_actions: vec![
"remove unsupported tool aliases from allowed tool list".into(),
"replace run.replay requests with a supported alias".into(),
],
required_verification: vec!["tool-policy".into()],
required_permits: Vec::new(),
risk_level: "low".into(),
canonical_owner: Some("aidens-tool-kit".into()),
});
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step: 1,
outcome: "abstained-unsupported-tools".into(),
final_state: "tool-policy-check".into(),
blocked: true,
support_label: self.spec.support_label.to_string(),
reason_codes: vec!["tool-policy-contains-unsupported-alias".into()],
});
return Ok(output);
}
let plan = PlanReceiptV1 {
receipt_id: display_only_unstable_id("agent-plan"),
plan_id: display_only_unstable_id("agent-plan"),
step: 1,
action: "bounded plan-act-verify".into(),
reason_codes: vec!["agent-spec-valid".into(), "tool-policy-authorized".into()],
};
let local_policy_uses_mock_fixture =
self.spec.provider_policy.provider == AgentProviderModeV1::Local;
let route_receipt = ToolRouteReceiptV1 {
receipt_id: display_only_unstable_id("tool-route"),
plan_id: plan.plan_id.clone(),
step: 1,
requested_tool_ids: mapped_tools.canonical.clone(),
exposed_tool_ids: mapped_tools.canonical.clone(),
reason_codes: if local_policy_uses_mock_fixture {
vec!["provider-policy-local-routed-to-explicit-mock-fixture".into()]
} else {
Vec::new()
},
};
let provider_kind = match self.spec.provider_policy.provider {
AgentProviderModeV1::Mock => "mock".to_string(),
AgentProviderModeV1::Local => "mock".to_string(),
AgentProviderModeV1::Ollama => {
if let Some(ref url) = self.provider_base_url {
if url.contains("opencode") || url.contains("/v1") {
"openai-compatible".to_string()
} else {
"ollama".to_string()
}
} else {
"ollama".to_string()
}
}
};
let provider_spec = ProviderSpecV1 {
kind: provider_kind,
model: self.provider_model.clone(),
api_key: self.provider_api_key.clone(),
base_url: self.provider_base_url.clone(),
mock_response: self.provider_mock_response.clone(),
};
if provider_spec.kind == "mock" && provider_spec.mock_response.as_deref().is_none() {
let mut output = PlanActVerifyLoopV1Output::abstention(
&self.spec,
1,
"provider-mock-response-missing",
);
output.memory_grounding_receipts = memory_grounding_receipts;
output.repair_plan = Some(RepairPlanDisplayReceiptV1 {
repair_id: display_only_unstable_id("agent-repair"),
source_run_id: None,
failure_kind: "provider-config".into(),
candidate_repair_actions: vec![
"configure a local provider mock response before execution".into(),
"validate local provider route and retry".into(),
],
required_verification: vec!["provider-routing".into()],
required_permits: Vec::new(),
risk_level: "high".into(),
canonical_owner: Some("aidens-provider-kit".into()),
});
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step: 1,
outcome: "abstained-setup".into(),
final_state: "provider-mock-missing".into(),
blocked: true,
support_label: self.spec.support_label.to_string(),
reason_codes: vec!["provider-mock-response-missing".into()],
});
return Ok(output);
}
let runner = AiDENsRunner::builder()
.app_id(self.app_id.clone())
.provider_spec(provider_spec)
.tools(self.tools.clone())
.permit_policy(self.permit_policy.clone())
.budget(BudgetV1 {
max_tool_calls: self.spec.budget_policy.max_tool_calls,
max_retries: self.budget_retries,
max_turn_millis: self
.spec
.budget_policy
.deadline_seconds
.saturating_mul(1000),
})
.run_reports(self.run_reports.clone())
.receipt_level(self.receipt_level.clone())
.agency_policy(self.agency_policy.clone())
.agency_nudge_ledger(self.agency_nudges.clone())
.governance(self.governance.clone())
.kernel(self.kernel)
.canonical_receipt_log_config(
self.canonical_receipt_log_config
.clone()
.unwrap_or_else(default_plan_act_verify_receipt_log_config),
)
.build()?;
let mut policy = ToolExposurePolicyV1::coding_agent_default();
if let Some(root) = self.tools.sandbox_root() {
policy = policy.with_sandbox_root(root.to_string_lossy().to_string());
} else if let Some(ref root) = self.sandbox_root {
policy = policy.with_sandbox_root(root.clone());
}
policy.allowed_tool_ids = Some(mapped_tools.canonical.iter().cloned().collect());
let mut output = PlanActVerifyLoopV1Output {
agent_id: self.spec.agent_id.clone(),
app_id: self.app_id.clone(),
profile: self.spec.profile.clone(),
support_label: self.spec.support_label.to_string(),
outcome: PlanActVerifyOutcomeV1::Failed,
turns_used: 0,
max_turns: self.spec.budget_policy.max_turns,
plan_receipts: vec![plan],
tool_route_receipts: vec![route_receipt],
tool_call_receipts: Vec::new(),
verification_receipts: Vec::new(),
memory_grounding_receipts,
abstention_receipt: None,
repair_plan: None,
finalization: None,
run_output: None,
};
let max_turns = self.spec.budget_policy.max_turns.max(1);
let run_input = AiDENsRunInput::new(prompt.clone());
for step in 1..=max_turns {
output.turns_used = step;
let turn_output = match runner
.run_with_tool_policy(run_input.clone(), policy.clone())
.await
{
Ok(run_output) => run_output,
Err(error) => {
output.outcome = PlanActVerifyOutcomeV1::Abstained;
output.abstention_receipt = Some(AbstentionReceiptV1 {
receipt_id: display_only_unstable_id("agent-abstention"),
step,
reason_code: format!("runner-execution-error:{error}"),
blocked_action: "provider/completion".into(),
evidence: vec![error.to_string()],
required_permits: Vec::new(),
can_resume: false,
support_impact: "degraded".into(),
});
output.repair_plan = Some(RepairPlanDisplayReceiptV1 {
repair_id: display_only_unstable_id("agent-repair"),
source_run_id: None,
failure_kind: "runner-execution".into(),
candidate_repair_actions: vec![
"collect provider and tool-chain evidence for the error".into(),
"retry with corrected local provider configuration".into(),
],
required_verification: vec!["runner".into()],
required_permits: Vec::new(),
risk_level: "high".into(),
canonical_owner: Some("aidens-provider-kit".into()),
});
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step,
outcome: "failed-runner-error".into(),
final_state: "failed".into(),
blocked: true,
support_label: self.spec.support_label.to_string(),
reason_codes: vec![error.to_string()],
});
return Ok(output);
}
};
let plan_id = output
.plan_receipts
.first()
.map(|p| p.plan_id.clone())
.unwrap_or_else(|| display_only_unstable_id("agent-plan"));
if let Some(ref memory) = self.memory {
match memory.search(&prompt, None, Some(5)).await {
Ok(results) => {
let grounding = MemoryGroundingEvidenceV1::canonical_seam(
"grounded-chat",
&prompt,
results.len(),
0,
"memory-grounding",
Vec::new(),
Vec::new(),
);
output.memory_grounding_receipts.push(
grounding.to_receipt_line().map_err(|error| {
anyhow!("memory-grounding-receipt-json:{error}")
})?,
);
}
Err(error) => {
let grounding = MemoryGroundingEvidenceV1::canonical_seam(
"grounded-chat",
&prompt,
0,
0,
"memory-grounding",
vec![format!("memory-search-failed:{error}")],
Vec::new(),
);
output.memory_grounding_receipts.push(
grounding.to_receipt_line().map_err(|error| {
anyhow!("memory-grounding-receipt-json:{error}")
})?,
);
}
}
}
output.run_output = Some(turn_output.clone());
output.tool_call_receipts.push(ToolCallReceiptV1 {
receipt_id: display_only_unstable_id("agent-tool-call"),
plan_id,
step,
run_id: turn_output.receipt.context.run_id.to_string(),
permitted_tool_calls: turn_output.receipt.permit_use_receipts.len(),
blocked_tool_calls: turn_output.receipt.approval_requests.len(),
succeeded_tool_calls: turn_output
.receipt
.tool_invocation_receipts
.iter()
.filter(|receipt| receipt.succeeded)
.count(),
failed_tool_calls: turn_output
.receipt
.tool_invocation_receipts
.iter()
.filter(|receipt| !receipt.succeeded)
.count(),
reason_codes: turn_output
.receipt
.tool_invocation_receipts
.iter()
.flat_map(|receipt| receipt.reason_codes.iter().cloned())
.collect(),
});
output
.verification_receipts
.extend(verification_checks_for_loop(
&self.spec,
&turn_output,
&mapped_tools.canonical,
));
let failed_checks = output
.verification_receipts
.iter()
.any(|receipt| !receipt.passed);
if failed_checks && self.spec.verification_policy.fail_closed {
output.outcome = PlanActVerifyOutcomeV1::Abstained;
let failed_checks = failed_verification_checks(&output.verification_receipts);
output.abstention_receipt = Some(AbstentionReceiptV1 {
receipt_id: display_only_unstable_id("agent-abstention"),
step,
reason_code: "verification-failed".into(),
blocked_action: "execution".into(),
evidence: vec![turn_output.receipt.receipt_id.to_string()],
required_permits: Vec::new(),
can_resume: false,
support_impact: "degraded".into(),
});
output.repair_plan = Some(RepairPlanDisplayReceiptV1 {
repair_id: display_only_unstable_id("agent-repair"),
source_run_id: Some(turn_output.receipt.context.run_id.to_string()),
failure_kind: "verification-failed".into(),
candidate_repair_actions: vec![
"re-run with fixed verification-policy inputs".into(),
"remove or correct unsupported tool-call evidence".into(),
],
required_verification: failed_checks,
required_permits: Vec::new(),
risk_level: "moderate".into(),
canonical_owner: Some("aidens-verification-kit".into()),
});
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step,
outcome: "abstained-verification".into(),
final_state: format!("{:?}", turn_output.turn_receipt.final_state),
blocked: true,
support_label: self.spec.support_label.to_string(),
reason_codes: vec!["verification-failed".into()],
});
return Ok(output);
}
if turn_output.turn_receipt.final_state == TurnFinalStateV1::FinalOutput {
output.outcome = PlanActVerifyOutcomeV1::Success;
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step,
outcome: "success".into(),
final_state: "FinalOutput".into(),
blocked: false,
support_label: self.spec.support_label.to_string(),
reason_codes: if local_policy_uses_mock_fixture {
vec!["provider-policy-local-routed-to-explicit-mock-fixture".into()]
} else {
Vec::new()
},
});
return Ok(output);
}
if turn_output.turn_receipt.blocked || turn_output.turn_receipt.degraded {
output.outcome = PlanActVerifyOutcomeV1::Abstained;
let stop_reasons = turn_output_block_reason_codes(&turn_output);
let blocked_action = blocked_turn_action(turn_output.turn_receipt.final_state);
output.abstention_receipt = Some(AbstentionReceiptV1 {
receipt_id: display_only_unstable_id("agent-abstention"),
step,
reason_code: format!("turn-blocked:{}", stop_reasons.join("|")),
blocked_action: blocked_action.into(),
evidence: {
let mut evidence = vec![turn_output.receipt.receipt_id.to_string()];
evidence.extend(stop_reasons);
evidence
},
required_permits: turn_output
.receipt
.approval_requests
.iter()
.map(|request| request.tool_id.clone())
.collect(),
can_resume: !turn_output.receipt.approval_requests.is_empty(),
support_impact: "deferred".into(),
});
output.repair_plan = Some(RepairPlanDisplayReceiptV1 {
repair_id: display_only_unstable_id("agent-repair"),
source_run_id: Some(turn_output.receipt.context.run_id.to_string()),
failure_kind: "blocked".into(),
candidate_repair_actions: vec![
"request permits for blocked tools".into(),
"inspect stop rule codes and adjust tool authority".into(),
"tighten tool policy to explicit writable tools".into(),
],
required_verification: turn_output
.receipt
.stop_rule_receipts
.iter()
.flat_map(|receipt| receipt.reason_codes.iter().cloned())
.collect(),
required_permits: turn_output
.receipt
.approval_requests
.iter()
.map(|request| request.tool_id.clone())
.collect(),
risk_level: "moderate".into(),
canonical_owner: Some("aidens-tool-kit".into()),
});
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step,
outcome: "abstained-blocked".into(),
final_state: format!("{:?}", turn_output.turn_receipt.final_state),
blocked: true,
support_label: self.spec.support_label.to_string(),
reason_codes: vec!["turn-blocked".into()],
});
return Ok(output);
}
if step >= max_turns {
output.outcome = PlanActVerifyOutcomeV1::Abstained;
output.abstention_receipt = Some(AbstentionReceiptV1 {
receipt_id: display_only_unstable_id("agent-abstention"),
step,
reason_code: "max-turns-exhausted".into(),
blocked_action: "repeat".into(),
evidence: vec![turn_output.receipt.receipt_id.to_string()],
required_permits: Vec::new(),
can_resume: true,
support_impact: "degraded".into(),
});
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step,
outcome: "abstained-bounds-exhausted".into(),
final_state: format!("{:?}", turn_output.turn_receipt.final_state),
blocked: true,
support_label: self.spec.support_label.to_string(),
reason_codes: vec!["max-turns-exhausted".into()],
});
return Ok(output);
}
}
output.outcome = PlanActVerifyOutcomeV1::RepairNeeded;
output.finalization = Some(FinalizationReceiptV1 {
receipt_id: display_only_unstable_id("agent-finalization"),
step: output.turns_used,
outcome: "repair-needed".into(),
final_state: "repair".into(),
blocked: true,
support_label: self.spec.support_label.to_string(),
reason_codes: vec!["loop-exited-without-success".into()],
});
Ok(output)
}
}
#[derive(Debug, Clone)]
pub struct AiDENsRunOutput {
pub text: String,
pub receipt: RunReportV1,
pub turn_receipt: TurnReportV1,
pub tool_exposure: ToolExposureSetV1,
pub agency_policy_reports: Vec<AgencyPolicyReportV1>,
pub memory_grounding_receipts: Vec<String>,
pub durable_receipt_records: Vec<CanonicalEventLogEntry>,
}
#[derive(Debug, Clone, Default)]
pub struct RunReportLedger {
reports: Arc<Mutex<Vec<RunReportV1>>>,
}
impl RunReportLedger {
pub fn append(&self, report: RunReportV1) -> usize {
let mut reports = self.lock_reports();
reports.push(report);
reports.len()
}
pub fn list(&self) -> Vec<RunReportV1> {
self.lock_reports().clone()
}
pub fn len(&self) -> usize {
self.lock_reports().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn lock_reports(&self) -> MutexGuard<'_, Vec<RunReportV1>> {
self.reports
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
pub fn disclose_runtime_view(
request: &RuntimeViewRequestV1,
projection_digest: ProjectionDigestV1,
matched_claim_ids: Vec<ArtifactId>,
query_widenings: &[QueryWideningReportV1],
degradations: &[DegradationEventV1],
) -> ViewDisclosureReportV1 {
ViewDisclosureReportV1::new(
request,
projection_digest,
matched_claim_ids,
query_widenings
.iter()
.map(|receipt| receipt.receipt_id.clone())
.collect(),
degradations
.iter()
.map(|event| event.event_id.clone())
.collect(),
)
}
#[derive(Clone)]
pub struct AiDENsRunner {
app_id: String,
provider: Arc<dyn AiDENsProvider>,
tools: ToolRegistryV1,
permit_policy: PermitPolicyV1,
budget: BudgetV1,
run_reports: RunReportLedger,
receipt_level: ReportLevelV1,
canonical_receipts: Option<CanonicalEventLog>,
agency_policy: AgencyPolicyEngineV1,
agency_nudges: Arc<Mutex<NudgeLedgerV1>>,
governance: Option<GovernanceContext>,
kernel: Option<CanonicalKernelAdapter>,
}
impl std::fmt::Debug for AiDENsRunner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AiDENsRunner")
.field("app_id", &self.app_id)
.field("provider_kind", &self.provider.provider_kind())
.field("model", &self.provider.model())
.field("tool_count", &self.tools.len())
.field("permit_policy", &"scoped")
.field("budget", &self.budget)
.field("receipt_level", &self.receipt_level)
.field("agency_policy", &"enabled")
.field("governance", &self.governance.is_some())
.field("kernel", &self.kernel.is_some())
.field(
"durable_receipts",
&self
.canonical_receipts
.as_ref()
.map(|store| store.config().root_path.display().to_string()),
)
.finish()
}
}
impl AiDENsRunner {
pub fn builder() -> AiDENsRunnerBuilder {
AiDENsRunnerBuilder::default()
}
pub async fn run(&self, input: AiDENsRunInput) -> anyhow::Result<AiDENsRunOutput> {
self.run_with_tool_policy(input, ToolExposurePolicyV1::coding_agent_default())
.await
}
pub async fn run_with_tool_policy(
&self,
input: AiDENsRunInput,
tool_exposure_policy: ToolExposurePolicyV1,
) -> anyhow::Result<AiDENsRunOutput> {
TurnExecutorV1::new(TurnExecutorConfigV1 {
app_id: self.app_id.clone(),
provider: self.provider.clone(),
tools: self.tools.clone(),
permit_policy: self.permit_policy.clone(),
budget: self.budget.clone(),
run_reports: self.run_reports.clone(),
receipt_level: self.receipt_level.clone(),
canonical_receipts: self.canonical_receipts.clone(),
agency_policy: self.agency_policy.clone(),
agency_nudges: self.agency_nudges.clone(),
governance: self.governance.clone(),
kernel: self.kernel,
})
.execute_with_tool_policy(input, tool_exposure_policy)
.await
}
pub fn run_reports(&self) -> RunReportLedger {
self.run_reports.clone()
}
pub fn receipt_level(&self) -> &ReportLevelV1 {
&self.receipt_level
}
pub fn canonical_receipt_log_config(&self) -> Option<&CanonicalEventLogConfig> {
self.canonical_receipts
.as_ref()
.map(CanonicalEventLog::config)
}
pub fn provider_route(&self) -> ProviderRouteReportV1 {
route_receipt(
self.provider.provider_kind(),
self.provider.model().map(str::to_string),
&self.provider.capabilities(),
)
}
pub fn tool_ids(&self) -> Vec<String> {
self.tools.tool_ids()
}
pub fn executable_tool_ids(&self) -> Vec<String> {
self.tools.executable_tool_ids()
}
pub fn tool_exposure(&self) -> ToolExposureSetV1 {
let provider_route = self.provider_route();
let tool_policy = ToolExposurePolicyV1::coding_agent_default()
.with_permit_policy(self.permit_policy.clone())
.for_provider_route(&provider_route);
self.tools.plan_exposure(&tool_policy)
}
}
#[derive(Clone)]
pub struct TurnExecutorV1 {
app_id: String,
provider: Arc<dyn AiDENsProvider>,
tools: ToolRegistryV1,
permit_policy: PermitPolicyV1,
budget: BudgetV1,
run_reports: RunReportLedger,
receipt_level: ReportLevelV1,
canonical_receipts: Option<CanonicalEventLog>,
agency_policy: AgencyPolicyEngineV1,
agency_nudges: Arc<Mutex<NudgeLedgerV1>>,
governance: Option<GovernanceContext>,
kernel: Option<CanonicalKernelAdapter>,
}
#[derive(Clone)]
pub struct TurnExecutorConfigV1 {
pub app_id: String,
pub provider: Arc<dyn AiDENsProvider>,
pub tools: ToolRegistryV1,
pub permit_policy: PermitPolicyV1,
pub budget: BudgetV1,
pub run_reports: RunReportLedger,
pub receipt_level: ReportLevelV1,
pub canonical_receipts: Option<CanonicalEventLog>,
pub agency_policy: AgencyPolicyEngineV1,
pub agency_nudges: Arc<Mutex<NudgeLedgerV1>>,
pub governance: Option<GovernanceContext>,
pub kernel: Option<CanonicalKernelAdapter>,
}
impl std::fmt::Debug for TurnExecutorV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TurnExecutorV1")
.field("app_id", &self.app_id)
.field("provider_kind", &self.provider.provider_kind())
.field("model", &self.provider.model())
.field("tool_count", &self.tools.len())
.field("permit_policy", &"scoped")
.field("budget", &self.budget)
.field("receipt_level", &self.receipt_level)
.field("governance", &self.governance.is_some())
.field("kernel", &self.kernel.is_some())
.finish()
}
}
impl TurnExecutorV1 {
pub fn new(config: TurnExecutorConfigV1) -> Self {
Self {
app_id: config.app_id,
provider: config.provider,
tools: config.tools,
permit_policy: config.permit_policy,
budget: config.budget,
run_reports: config.run_reports,
receipt_level: config.receipt_level,
canonical_receipts: config.canonical_receipts,
agency_policy: config.agency_policy,
agency_nudges: config.agency_nudges,
governance: config.governance,
kernel: config.kernel,
}
}
pub async fn execute(&self, input: AiDENsRunInput) -> anyhow::Result<AiDENsRunOutput> {
self.execute_with_tool_policy(input, ToolExposurePolicyV1::coding_agent_default())
.await
}
pub async fn execute_with_tool_policy(
&self,
input: AiDENsRunInput,
mut tool_policy: ToolExposurePolicyV1,
) -> anyhow::Result<AiDENsRunOutput> {
let ctx = AidensRunContextV1::new(&self.app_id);
let provider_route = route_receipt(
self.provider.provider_kind(),
self.provider.model().map(str::to_string),
&self.provider.capabilities(),
);
tool_policy = tool_policy
.with_permit_policy(self.permit_policy.clone())
.for_provider_route(&provider_route);
let tool_exposure = self.tools.plan_exposure(&tool_policy);
let mode = turn_mode_for(&provider_route, &tool_exposure);
let plan = TurnExecutionPlanV1::new(
mode,
provider_route.clone(),
&tool_exposure,
self.budget.max_tool_calls,
self.budget.max_retries,
self.budget.max_turn_millis,
turn_plan_reasons(mode),
);
let mut run_receipt = RunReportV1::started(ctx.clone());
run_receipt.provider_route = Some(provider_route.clone());
run_receipt
.tool_exposure_ids
.push(tool_exposure.exposure_id.clone());
run_receipt
.approval_requests
.extend(tool_exposure.approval_requests.clone());
run_receipt
.permit_use_receipts
.extend(tool_exposure.permit_use_receipts.clone());
if provider_route.degraded {
run_receipt.warnings.push(format!(
"provider-route-degraded:{}",
provider_route.route_label
));
}
for reason in &provider_route.reason_codes {
if reason.contains("fallback") || reason.contains("unknown-native-provider") {
run_receipt
.warnings
.push(format!("provider-route:{reason}"));
}
}
for reason in &tool_exposure.reason_codes {
run_receipt.warnings.push(format!("tool-exposure:{reason}"));
}
let mut turn_receipt = TurnReportV1::started(&ctx, &plan);
if mode == TurnModeV1::ProviderUnavailable {
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::ProviderUnavailable,
provider_route.reason_codes.clone(),
);
turn_receipt.record_stop_rule(&stop);
let completed_turn = turn_receipt.complete(TurnFinalStateV1::ProviderUnavailable);
run_receipt.stop_rule_receipts.push(stop);
run_receipt.turn_receipts.push(completed_turn);
run_receipt.warnings.push("provider-unavailable".into());
let completed = run_receipt.complete();
let control_records = self.failure_control_records(
&ctx,
"provider-route",
&provider_route.reason_codes,
serde_json::json!({
"provider_kind": provider_route.provider_kind.clone(),
"route_label": provider_route.route_label.clone(),
"route": provider_route.route,
"degraded": provider_route.degraded,
}),
)?;
self.persist_completed_with_records(completed, &tool_exposure, control_records)?;
return Err(anyhow!(
"provider unavailable: {}",
provider_route.reason_codes.join(",")
));
}
let mut tool_results = Vec::<ToolCallResultV1>::new();
let mut current_turn_tool_calls = Vec::<ToolCallRequestV1>::new();
let mut agency_policy_reports = Vec::<AgencyPolicyReportV1>::new();
let mut tool_calls_so_far = 0u32;
let mut retries = 0u32;
let mut seen_tool_calls = BTreeSet::<String>::new();
let started_at = Instant::now();
let exposed_tool_ids = tool_exposure
.exposed_tool_ids
.iter()
.cloned()
.collect::<BTreeSet<_>>();
loop {
seen_tool_calls.clear();
let elapsed = elapsed_millis(started_at);
if self.budget.deadline_exceeded(elapsed) {
return self.finish_budget_exhausted(BudgetStopInput {
ctx,
run_receipt,
turn_receipt,
tool_exposure,
attempted_tool_calls: tool_calls_so_far,
retries,
elapsed_millis: elapsed,
reason: "turn-deadline-exceeded".into(),
agency_policy_reports,
});
}
let request = match completion_request(
input.prompt.clone(),
&tool_exposure,
&tool_results,
¤t_turn_tool_calls,
) {
Ok(request) => request,
Err(error) => {
let reason_codes = vec!["tool-result-serialization-failed".into()];
run_receipt
.warnings
.push(format!("provider-request-error:{error}"));
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::ToolInvocationFailed,
reason_codes.clone(),
);
turn_receipt.record_stop_rule(&stop);
turn_receipt.degraded = true;
turn_receipt.blocked = true;
run_receipt.stop_rule_receipts.push(stop);
let completed_turn = turn_receipt.complete(TurnFinalStateV1::ToolFailed);
run_receipt.turn_receipts.push(completed_turn);
let completed = run_receipt.complete();
let control_records = self.failure_control_records(
&ctx,
"provider-request",
&reason_codes,
serde_json::json!({
"provider_kind": provider_route.provider_kind.clone(),
"route_label": provider_route.route_label.clone(),
"error": error.to_string(),
}),
)?;
self.persist_completed_with_records(
completed,
&tool_exposure,
control_records,
)?;
return Err(error);
}
};
let completion = match self.provider.complete(request).await {
Ok(completion) => completion,
Err(error) if self.budget.allows_retry(retries) => {
retries += 1;
run_receipt.warnings.push(format!("provider-retry:{error}"));
continue;
}
Err(error) => {
run_receipt.warnings.push(format!("provider-error:{error}"));
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::MaxRetries,
vec!["provider-error-max-retries".into()],
);
turn_receipt.record_stop_rule(&stop);
let completed_turn = turn_receipt.complete(TurnFinalStateV1::StopRuleTriggered);
run_receipt.stop_rule_receipts.push(stop);
run_receipt.turn_receipts.push(completed_turn);
let completed = run_receipt.complete();
let control_records = self.failure_control_records(
&ctx,
"provider-error",
&["provider-error-max-retries".into()],
serde_json::json!({
"provider_kind": provider_route.provider_kind.clone(),
"route_label": provider_route.route_label.clone(),
"error": error.to_string(),
}),
)?;
self.persist_completed_with_records(
completed,
&tool_exposure,
control_records,
)?;
return Err(error);
}
};
let completion_tool_calls = tool_calls_for_completion(&completion, mode);
run_receipt
.boundary_repair_receipts
.extend(completion_tool_calls.boundary_repair_receipts);
run_receipt
.json_repair_receipts
.extend(completion_tool_calls.json_repair_receipts);
if !completion_tool_calls.degradation_reason_codes.is_empty() {
let reason_codes = completion_tool_calls.degradation_reason_codes;
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::ToolInvocationFailed,
reason_codes.clone(),
);
turn_receipt.record_stop_rule(&stop);
turn_receipt.degraded = true;
turn_receipt.blocked = true;
run_receipt.stop_rule_receipts.push(stop);
run_receipt.warnings.extend(
reason_codes
.iter()
.map(|reason| format!("tool-call-degraded:{reason}")),
);
let completed_turn = turn_receipt.complete(TurnFinalStateV1::ToolFailed);
run_receipt.turn_receipts.push(completed_turn.clone());
let completed = run_receipt.complete();
let control_records = self.failure_control_records(
&ctx,
"parser-fallback-tool-call",
&reason_codes,
serde_json::json!({
"provider_kind": provider_route.provider_kind.clone(),
"route_label": provider_route.route_label.clone(),
"provider_text": completion.text.clone(),
}),
)?;
let (completed, durable_receipt_records) = self.persist_completed_with_records(
completed,
&tool_exposure,
control_records,
)?;
return Ok(AiDENsRunOutput {
text: "Turn stopped: malformed parser-fallback tool call.".into(),
receipt: completed,
turn_receipt: completed_turn,
tool_exposure,
agency_policy_reports,
memory_grounding_receipts: Vec::new(),
durable_receipt_records,
});
}
let tool_calls = completion_tool_calls.calls;
current_turn_tool_calls.clear();
current_turn_tool_calls.extend(tool_calls.iter().cloned());
if tool_calls.is_empty() {
let tool_result_texts = tool_results
.iter()
.map(ToolCallResultV1::output_text)
.filter(|text| !text.trim().is_empty())
.collect::<Vec<_>>();
let agency_input = AgencyPolicyInputV1::for_runner_final_output(
input.prompt.clone(),
completion.text.clone(),
&tool_result_texts,
);
let agency_report = self.evaluate_agency_policy(&agency_input);
run_receipt
.agency_receipt_ids
.extend(agency_report.receipt_ids());
run_receipt.warnings.extend(
agency_report
.reason_codes()
.iter()
.map(|reason| format!("agency-policy:{reason}")),
);
let final_text = final_text_after_agency_gate(&completion.text, &agency_report);
let agency_outcome = agency_report.outcome;
agency_policy_reports.push(agency_report);
if !agency_outcome.allows_direct_output() {
let reason_codes = vec![format!(
"agency-policy:{}",
agency_outcome.as_policy_label()
)];
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::AgencyPolicy,
reason_codes.clone(),
);
turn_receipt.record_stop_rule(&stop);
turn_receipt.degraded = true;
turn_receipt.blocked = true;
run_receipt.stop_rule_receipts.push(stop);
run_receipt.warnings.extend(reason_codes.clone());
let completed_turn = turn_receipt.complete(TurnFinalStateV1::StopRuleTriggered);
run_receipt.turn_receipts.push(completed_turn.clone());
let completed = run_receipt.complete();
let mut control_records = self.turn_control_records(
&ctx,
"agency-policy",
&reason_codes,
serde_json::json!({
"control_decision": agency_outcome.as_policy_label(),
"agency_reports": &agency_policy_reports,
"provider_kind": provider_route.provider_kind.clone(),
"route_label": provider_route.route_label.clone(),
"turn_receipt_id": completed_turn.receipt_id.clone(),
"turn_final_state": completed_turn.final_state,
"tool_exposure_id": tool_exposure.exposure_id.clone(),
}),
true,
governance_stack::VerificationAttemptState::Blocked,
)?;
control_records.extend(self.agency_policy_records(&agency_policy_reports)?);
let (completed, durable_receipt_records) = self
.persist_completed_with_records(
completed,
&tool_exposure,
control_records,
)?;
return Ok(AiDENsRunOutput {
text: final_text,
receipt: completed,
turn_receipt: completed_turn,
tool_exposure,
agency_policy_reports,
memory_grounding_receipts: Vec::new(),
durable_receipt_records,
});
}
let reason_codes = vec!["final-output-produced".into()];
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::FinalOutput,
reason_codes.clone(),
);
turn_receipt.record_stop_rule(&stop);
let completed_turn = turn_receipt.complete(TurnFinalStateV1::FinalOutput);
run_receipt.stop_rule_receipts.push(stop);
run_receipt.turn_receipts.push(completed_turn.clone());
let completed = run_receipt.complete();
let mut control_records = self.turn_control_records(
&ctx,
"final-output",
&reason_codes,
serde_json::json!({
"control_decision": "final-output-produced",
"provider_kind": provider_route.provider_kind.clone(),
"route_label": provider_route.route_label.clone(),
"turn_receipt_id": completed_turn.receipt_id.clone(),
"turn_final_state": completed_turn.final_state,
"tool_exposure_id": tool_exposure.exposure_id.clone(),
"exposed_tool_ids": tool_exposure.exposed_tool_ids.clone(),
}),
completed_turn.degraded,
governance_stack::VerificationAttemptState::AdvisoryOnly,
)?;
control_records.extend(self.agency_policy_records(&agency_policy_reports)?);
let (completed, durable_receipt_records) = self.persist_completed_with_records(
completed,
&tool_exposure,
control_records,
)?;
return Ok(AiDENsRunOutput {
text: final_text,
receipt: completed,
turn_receipt: completed_turn,
tool_exposure,
agency_policy_reports,
memory_grounding_receipts: Vec::new(),
durable_receipt_records,
});
}
let requested_tool_calls = tool_calls.len() as u32;
if !self
.budget
.allows_tool_call(tool_calls_so_far, requested_tool_calls)
{
for tool_call in tool_calls {
let invocation = ToolInvocationReportV1::started(
tool_call.tool_id.clone(),
tool_call.input.clone(),
)
.with_execution_context(&ctx)
.complete_failure("budget-exhausted-before-dispatch");
let result = ToolCallResultV1::from_invocation(&tool_call, &invocation);
turn_receipt.record_tool_call(&tool_call, &invocation);
run_receipt.tool_call_requests.push(tool_call);
run_receipt.tool_call_results.push(result);
run_receipt.tool_invocation_receipts.push(invocation);
}
return self.finish_budget_exhausted(BudgetStopInput {
ctx,
run_receipt,
turn_receipt,
tool_exposure,
attempted_tool_calls: tool_calls_so_far.saturating_add(requested_tool_calls),
retries,
elapsed_millis: elapsed_millis(started_at),
reason: "max-tool-calls-exhausted".into(),
agency_policy_reports,
});
}
let dispatcher = ToolDispatcher::new(self.tools.clone())
.with_permit_policy(self.permit_policy.clone());
for tool_call in tool_calls {
let call_signature = format!("{}:{}", tool_call.tool_id, tool_call.input_digest);
if !seen_tool_calls.insert(call_signature) {
let invocation = ToolInvocationReportV1::started(
tool_call.tool_id.clone(),
tool_call.input.clone(),
)
.with_execution_context(&ctx)
.complete_failure("recursive-tool-call");
let result = ToolCallResultV1::from_invocation(&tool_call, &invocation);
turn_receipt.record_tool_call(&tool_call, &invocation);
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::RecursiveToolCall,
vec!["recursive-tool-call".into()],
);
turn_receipt.record_stop_rule(&stop);
return self.finish_blocked_turn(BlockedTurnInput {
run_receipt,
turn_receipt,
tool_exposure,
tool_call,
invocation,
result,
stop_rule: stop,
text: "Turn stopped: recursive tool call was blocked.".into(),
final_state: TurnFinalStateV1::ToolBlocked,
agency_policy_reports,
durable_receipt_records: Vec::new(),
});
}
if !exposed_tool_ids.contains(&tool_call.tool_id) {
let invocation = ToolInvocationReportV1::started(
tool_call.tool_id.clone(),
tool_call.input.clone(),
)
.with_execution_context(&ctx)
.complete_failure("tool-not-exposed-this-turn");
let result = ToolCallResultV1::from_invocation(&tool_call, &invocation);
turn_receipt.record_tool_call(&tool_call, &invocation);
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::ToolNotExposed,
vec!["tool-not-exposed-this-turn".into()],
);
turn_receipt.record_stop_rule(&stop);
return self.finish_blocked_turn(BlockedTurnInput {
run_receipt,
turn_receipt,
tool_exposure,
tool_call,
invocation,
result,
stop_rule: stop,
text: "Turn stopped: provider requested a tool that was not exposed."
.into(),
final_state: TurnFinalStateV1::ToolBlocked,
agency_policy_reports,
durable_receipt_records: Vec::new(),
});
}
let governance_case_plan = self.governance_tool_case_plan(&ctx, &tool_call)?;
if let Some((case, plan)) = &governance_case_plan {
if let Some(governance) = &self.governance {
if let Err(error) = governance.check_permit(case, plan) {
let reason_codes = vec![format!("governance-permit-denied:{error}")];
let invocation = ToolInvocationReportV1::started(
tool_call.tool_id.clone(),
tool_call.input.clone(),
)
.with_execution_context(&ctx)
.complete_failure(reason_codes[0].clone());
let result = ToolCallResultV1::from_invocation(&tool_call, &invocation);
turn_receipt.record_tool_call(&tool_call, &invocation);
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::ToolInvocationFailed,
reason_codes.clone(),
);
turn_receipt.record_stop_rule(&stop);
let control_records = self.governance_tool_control_records(
case,
plan,
governance_stack::VerificationAttemptState::Blocked,
&reason_codes.join(","),
serde_json::json!({
"tool_call": &tool_call,
"permit_error": error.to_string(),
}),
false,
)?;
return self.finish_blocked_turn(BlockedTurnInput {
run_receipt,
turn_receipt,
tool_exposure,
tool_call,
invocation,
result,
stop_rule: stop,
text: "Turn stopped: governance permit denied tool dispatch."
.into(),
final_state: TurnFinalStateV1::ToolBlocked,
agency_policy_reports,
durable_receipt_records: control_records,
});
}
}
}
let invocation = match dispatcher
.invoke(&tool_call.tool_id, tool_call.input.clone())
.await
{
Ok(outcome) => {
if let Some(mut permit_use_receipt) = outcome.permit_use_receipt {
permit_use_receipt.run_id = Some(ctx.run_id.clone());
permit_use_receipt.attempt_id = Some(ctx.attempt_id.clone());
run_receipt.permit_use_receipts.push(permit_use_receipt);
}
outcome.receipt.with_execution_context(&ctx)
}
Err(error) => {
if let Some(invocation_error) = error.downcast_ref::<ToolInvocationError>()
{
if let Some(approval_request) = invocation_error.approval_request() {
run_receipt.approval_requests.push(approval_request.clone());
}
if let Some(schema_validation) =
invocation_error.schema_validation_receipt()
{
run_receipt
.schema_validation_receipts
.push(schema_validation.clone().with_execution_context(&ctx));
}
}
tool_invocation_receipt_from_error(
&tool_call.tool_id,
tool_call.input.clone(),
&error,
&ctx,
)
}
};
let result = ToolCallResultV1::from_invocation(&tool_call, &invocation);
if let Some((case, plan)) = &governance_case_plan {
let state = if invocation.succeeded {
governance_stack::VerificationAttemptState::Succeeded
} else {
governance_stack::VerificationAttemptState::Failed
};
let control_records = self.governance_tool_control_records(
case,
plan,
state,
if invocation.succeeded {
"tool-dispatch-completed"
} else {
"tool-dispatch-failed"
},
serde_json::json!({
"tool_invocation": &invocation,
"tool_result": &result,
}),
false,
)?;
run_receipt.warnings.extend(
control_records
.iter()
.map(|record| format!("governance-control:{}", record.receipt_id)),
);
}
turn_receipt.record_tool_call(&tool_call, &invocation);
run_receipt.tool_call_requests.push(tool_call.clone());
run_receipt.tool_call_results.push(result.clone());
run_receipt
.tool_invocation_receipts
.push(invocation.clone());
tool_results.push(result.clone());
tool_calls_so_far += 1;
if result.succeeded {
let tool_output = result.output_text();
if !tool_output.trim().is_empty() {
let agency_input = AgencyPolicyInputV1::for_runner_tool_output(
input.prompt.clone(),
tool_output,
);
let agency_report = self.evaluate_agency_policy(&agency_input);
let agency_outcome = agency_report.outcome;
run_receipt
.agency_receipt_ids
.extend(agency_report.receipt_ids());
run_receipt.warnings.extend(
agency_report
.reason_codes()
.iter()
.map(|reason| format!("agency-policy:{reason}")),
);
agency_policy_reports.push(agency_report);
if !agency_outcome.allows_direct_output() {
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::AgencyPolicy,
vec![format!(
"agency-policy:{}",
agency_outcome.as_policy_label()
)],
);
turn_receipt.record_stop_rule(&stop);
run_receipt.stop_rule_receipts.push(stop);
turn_receipt.degraded = true;
turn_receipt.blocked = true;
let completed_turn =
turn_receipt.complete(TurnFinalStateV1::StopRuleTriggered);
run_receipt.turn_receipts.push(completed_turn.clone());
let completed = run_receipt.complete();
let mut control_records = self.failure_control_records(
&ctx,
"agency-tool-output-policy",
&[format!(
"agency-policy:{}",
agency_outcome.as_policy_label()
)],
serde_json::json!({
"agency_reports": &agency_policy_reports,
"tool_result": result,
}),
)?;
control_records
.extend(self.agency_policy_records(&agency_policy_reports)?);
let (completed, durable_receipt_records) = self
.persist_completed_with_records(
completed,
&tool_exposure,
control_records,
)?;
return Ok(AiDENsRunOutput {
text: final_text_for_agency_block(agency_outcome),
receipt: completed,
turn_receipt: completed_turn,
tool_exposure,
agency_policy_reports,
memory_grounding_receipts: Vec::new(),
durable_receipt_records,
});
}
}
}
if !invocation.succeeded {
let stop = StopRuleReportV1::triggered(
&ctx,
StopRuleV1::ToolInvocationFailed,
invocation.reason_codes.clone(),
);
turn_receipt.record_stop_rule(&stop);
run_receipt.stop_rule_receipts.push(stop);
turn_receipt.degraded = true;
let completed_turn = turn_receipt.complete(TurnFinalStateV1::ToolFailed);
run_receipt.turn_receipts.push(completed_turn.clone());
run_receipt.warnings.push("tool-invocation-failed".into());
let completed = run_receipt.complete();
let control_records = self.failure_control_records(
&ctx,
&invocation.tool_id,
&invocation.reason_codes,
serde_json::json!({
"tool_invocation": invocation,
"tool_result": result,
}),
)?;
let (completed, durable_receipt_records) = self
.persist_completed_with_records(
completed,
&tool_exposure,
control_records,
)?;
return Ok(AiDENsRunOutput {
text: "Turn stopped: tool invocation failed.".into(),
receipt: completed,
turn_receipt: completed_turn,
tool_exposure,
agency_policy_reports,
memory_grounding_receipts: Vec::new(),
durable_receipt_records,
});
}
}
}
}
fn governance_tool_case_plan(
&self,
context: &AidensRunContextV1,
tool_call: &ToolCallRequestV1,
) -> anyhow::Result<
Option<(
governance_stack::VerificationCase,
governance_stack::CheckPlan,
)>,
> {
let Some(governance) = &self.governance else {
return Ok(None);
};
let case = governance.open_case(
governance_stack::VerificationCaseClass::QueryTurn,
self.app_id.clone(),
format!("tool-dispatch:{}", tool_call.tool_id),
context.stack_trace_ctx(),
context.stack_attempt_id(),
);
let plan = CanonicalGovernanceAdapter.check_plan(
&case,
governance_stack::CheckMethod::CausalRefuter,
vec!["tool-dispatch-permit".into()],
governance_stack::PromotionClass::P3,
governance_stack::ReversibilityClass::ReversibleLocal,
true,
false,
false,
"runner governance pre-dispatch permit check",
serde_json::json!({
"control_surface": "runner-tool-dispatch",
"tool_id": &tool_call.tool_id,
"input_digest": &tool_call.input_digest,
}),
);
Ok(Some((case, plan)))
}
fn governance_tool_control_records(
&self,
case: &governance_stack::VerificationCase,
plan: &governance_stack::CheckPlan,
state: governance_stack::VerificationAttemptState,
outcome_signature: &str,
details: serde_json::Value,
promotable: bool,
) -> anyhow::Result<Vec<CanonicalEventLogEntry>> {
let recorded_at = current_recorded_at_label()?;
let adapter = CanonicalGovernanceAdapter;
let attempt = adapter.completed_attempt(
case,
plan,
state,
recorded_at.clone(),
recorded_at.clone(),
Some(outcome_signature.to_string()),
);
let receipt =
adapter.control_receipt_for_attempt(case, plan, &attempt, promotable, details);
if let Some(store) = &self.canonical_receipts {
return Ok(vec![store.append_control_receipt(&receipt)?]);
}
Ok(vec![CanonicalEventLogEntry::new(
"verification-control",
"in-memory-control-receipt",
receipt.receipt_id.to_string(),
serde_json::to_value(&receipt)?,
)?])
}
fn evaluate_agency_policy(&self, input: &AgencyPolicyInputV1) -> AgencyPolicyReportV1 {
let (mut ledger, recovered_from_poison) = match self.agency_nudges.lock() {
Ok(ledger) => (ledger, false),
Err(poisoned) => (poisoned.into_inner(), true),
};
let mut report = self.agency_policy.evaluate(input, &mut ledger);
if recovered_from_poison {
report
.decision
.reason_codes
.push("agency-nudge-ledger-poison-recovered".into());
report.decision.reason_codes.sort();
report.decision.reason_codes.dedup();
report.receipts.decision.reason_codes = report.decision.reason_codes.clone();
}
report
}
fn agency_policy_records(
&self,
reports: &[AgencyPolicyReportV1],
) -> anyhow::Result<Vec<CanonicalEventLogEntry>> {
let Some(store) = &self.canonical_receipts else {
return Ok(Vec::new());
};
let mut records = Vec::new();
for report in reports {
records.push(store.append_json(
"aidens-agency-kit",
"agency-policy-report-v1",
report.report_id.clone(),
serde_json::to_value(report)?,
)?);
}
Ok(records)
}
fn persist_completed_with_records(
&self,
completed: RunReportV1,
tool_exposure: &ToolExposureSetV1,
mut durable_receipt_records: Vec<CanonicalEventLogEntry>,
) -> anyhow::Result<(RunReportV1, Vec<CanonicalEventLogEntry>)> {
self.run_reports.append(completed.clone());
if let Some(store) = &self.canonical_receipts {
let tool_exposure_record_id =
format!("{}:{}", tool_exposure.exposure_id, completed.receipt_id);
durable_receipt_records.push(store.append_orchestration_report(
"tool-exposure-plan-v1",
tool_exposure_record_id,
serde_json::to_value(tool_exposure)?,
)?);
durable_receipt_records.push(store.append_orchestration_report(
"run-report-v1",
completed.receipt_id.to_string(),
serde_json::to_value(&completed)?,
)?);
}
Ok((completed, durable_receipt_records))
}
fn turn_control_records(
&self,
context: &AidensRunContextV1,
target_key: &str,
reason_codes: &[String],
details: serde_json::Value,
degraded: bool,
state: governance_stack::VerificationAttemptState,
) -> anyhow::Result<Vec<CanonicalEventLogEntry>> {
let Some(store) = &self.canonical_receipts else {
return Ok(Vec::new());
};
let adapter = CanonicalGovernanceAdapter;
let recorded_at = context.started_at.to_rfc3339();
let reason_summary = if reason_codes.is_empty() {
"turn-control-decision".to_string()
} else {
reason_codes.join(",")
};
let receipt_details = serde_json::json!({
"control_surface": "runner-turn",
"reason_codes": reason_codes,
"verification_attempt_state": state,
"details": details,
});
let case = adapter.verification_case(
governance_stack::VerificationCaseClass::QueryTurn,
self.app_id.clone(),
target_key.to_string(),
context.stack_trace_ctx(),
context.stack_attempt_id(),
recorded_at.clone(),
degraded,
true,
);
let plan = adapter.check_plan(
&case,
governance_stack::CheckMethod::AdvisoryOnly,
reason_codes.to_vec(),
governance_stack::PromotionClass::P3,
governance_stack::ReversibilityClass::ReversibleLocal,
false,
true,
degraded,
format!("runner turn control decision: {reason_summary}"),
receipt_details.clone(),
);
let attempt = adapter.completed_attempt(
&case,
&plan,
state,
recorded_at.clone(),
recorded_at,
Some(reason_summary),
);
let receipt =
adapter.control_receipt_for_attempt(&case, &plan, &attempt, false, receipt_details);
Ok(vec![store.append_control_receipt(&receipt)?])
}
fn failure_control_records(
&self,
context: &AidensRunContextV1,
target_key: &str,
reason_codes: &[String],
details: serde_json::Value,
) -> anyhow::Result<Vec<CanonicalEventLogEntry>> {
let Some(store) = &self.canonical_receipts else {
let recorded_at = context.started_at.to_rfc3339();
let reason_summary = if reason_codes.is_empty() {
"failure-honesty-degraded".to_string()
} else {
reason_codes.join(",")
};
let non_durable = serde_json::json!({
"durable": false,
"phase": "04-failure-honesty",
"reason_summary": reason_summary,
"reason_codes": reason_codes,
"details": details,
"target_key": target_key,
"app_id": self.app_id.clone(),
});
let receipt = CanonicalEventLogEntry::new(
"aidens-runner",
"in-memory-control-receipt",
format!("failure-control:{target_key}:{recorded_at}"),
non_durable,
)?;
return Ok(vec![receipt]);
};
let adapter = CanonicalGovernanceAdapter;
let recorded_at = context.started_at.to_rfc3339();
let reason_summary = if reason_codes.is_empty() {
"failure-honesty-degraded".to_string()
} else {
reason_codes.join(",")
};
let receipt_details = serde_json::json!({
"phase": "04-failure-honesty",
"reason_codes": reason_codes,
"details": details,
});
let case = adapter.verification_case(
governance_stack::VerificationCaseClass::QueryTurn,
self.app_id.clone(),
target_key.to_string(),
context.stack_trace_ctx(),
context.stack_attempt_id(),
recorded_at.clone(),
true,
true,
);
let plan = adapter.check_plan(
&case,
governance_stack::CheckMethod::AdvisoryOnly,
reason_codes.to_vec(),
governance_stack::PromotionClass::P3,
governance_stack::ReversibilityClass::ReversibleLocal,
false,
true,
true,
format!("failure honesty degradation: {reason_summary}"),
receipt_details.clone(),
);
let attempt = adapter.completed_attempt(
&case,
&plan,
governance_stack::VerificationAttemptState::Blocked,
recorded_at.clone(),
recorded_at,
Some(reason_summary),
);
let receipt =
adapter.control_receipt_for_attempt(&case, &plan, &attempt, false, receipt_details);
Ok(vec![store.append_control_receipt(&receipt)?])
}
fn finish_budget_exhausted(&self, input: BudgetStopInput) -> anyhow::Result<AiDENsRunOutput> {
let BudgetStopInput {
ctx,
mut run_receipt,
mut turn_receipt,
tool_exposure,
attempted_tool_calls,
retries,
elapsed_millis,
reason,
agency_policy_reports,
} = input;
let budget_receipt = BudgetExhaustionReportV1::new(BudgetExhaustionReportDraftV1 {
run_id: ctx.run_id.clone(),
attempt_id: ctx.attempt_id.clone(),
max_tool_calls: self.budget.max_tool_calls,
attempted_tool_calls,
max_retries: self.budget.max_retries,
retries,
max_turn_millis: self.budget.max_turn_millis,
elapsed_millis,
reason_codes: vec![reason.clone()],
});
let stop_rule = StopRuleReportV1::triggered(
&ctx,
if reason == "turn-deadline-exceeded" {
StopRuleV1::DeadlineExceeded
} else {
StopRuleV1::MaxToolCalls
},
vec![reason.clone()],
);
turn_receipt.record_budget_exhaustion(&budget_receipt);
turn_receipt.record_stop_rule(&stop_rule);
let completed_turn = turn_receipt.complete(TurnFinalStateV1::BudgetExhausted);
let control_records = self.failure_control_records(
&ctx,
"budget-exhaustion",
std::slice::from_ref(&reason),
serde_json::json!({
"budget_exhaustion": budget_receipt.clone(),
}),
)?;
run_receipt.budget_exhaustion_receipts.push(budget_receipt);
run_receipt.stop_rule_receipts.push(stop_rule);
run_receipt.turn_receipts.push(completed_turn.clone());
run_receipt
.warnings
.push(format!("budget-exhausted:{reason}"));
let completed = run_receipt.complete();
let (completed, durable_receipt_records) =
self.persist_completed_with_records(completed, &tool_exposure, control_records)?;
Ok(AiDENsRunOutput {
text: format!("Turn stopped: budget exhausted ({reason})."),
receipt: completed,
turn_receipt: completed_turn,
tool_exposure,
agency_policy_reports,
memory_grounding_receipts: Vec::new(),
durable_receipt_records,
})
}
fn finish_blocked_turn(&self, input: BlockedTurnInput) -> anyhow::Result<AiDENsRunOutput> {
let BlockedTurnInput {
mut run_receipt,
mut turn_receipt,
tool_exposure,
tool_call,
invocation,
result,
stop_rule,
text,
final_state,
agency_policy_reports,
durable_receipt_records,
} = input;
run_receipt.tool_call_requests.push(tool_call);
run_receipt.tool_call_results.push(result);
run_receipt.tool_invocation_receipts.push(invocation);
run_receipt.stop_rule_receipts.push(stop_rule);
turn_receipt.degraded = true;
turn_receipt.blocked = true;
let completed_turn = turn_receipt.complete(final_state);
run_receipt.turn_receipts.push(completed_turn.clone());
run_receipt.warnings.push("turn-blocked".into());
let completed = run_receipt.complete();
let (completed, durable_receipt_records) = self.persist_completed_with_records(
completed,
&tool_exposure,
durable_receipt_records,
)?;
Ok(AiDENsRunOutput {
text,
receipt: completed,
turn_receipt: completed_turn,
tool_exposure,
agency_policy_reports,
memory_grounding_receipts: Vec::new(),
durable_receipt_records,
})
}
}
struct BudgetStopInput {
ctx: AidensRunContextV1,
run_receipt: RunReportV1,
turn_receipt: TurnReportV1,
tool_exposure: ToolExposureSetV1,
attempted_tool_calls: u32,
retries: u32,
elapsed_millis: u64,
reason: String,
agency_policy_reports: Vec<AgencyPolicyReportV1>,
}
struct BlockedTurnInput {
run_receipt: RunReportV1,
turn_receipt: TurnReportV1,
tool_exposure: ToolExposureSetV1,
tool_call: ToolCallRequestV1,
invocation: ToolInvocationReportV1,
result: ToolCallResultV1,
stop_rule: StopRuleReportV1,
text: String,
final_state: TurnFinalStateV1,
agency_policy_reports: Vec<AgencyPolicyReportV1>,
durable_receipt_records: Vec<CanonicalEventLogEntry>,
}
fn current_recorded_at_label() -> anyhow::Result<String> {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|error| anyhow!("recorded-at-clock-error:{error}"))?
.as_nanos();
Ok(format!("unix-nanos:{nanos}"))
}
#[derive(Clone)]
pub struct AiDENsRunnerBuilder {
app_id: String,
provider: Option<Arc<dyn AiDENsProvider>>,
provider_spec: ProviderSpecV1,
tools: ToolRegistryV1,
permit_policy: PermitPolicyV1,
budget: BudgetV1,
run_reports: RunReportLedger,
receipt_level: ReportLevelV1,
canonical_receipt_log_config: Option<CanonicalEventLogConfig>,
agency_policy: AgencyPolicyEngineV1,
agency_nudges: Arc<Mutex<NudgeLedgerV1>>,
governance: Option<GovernanceContext>,
kernel: Option<CanonicalKernelAdapter>,
}
impl Default for AiDENsRunnerBuilder {
fn default() -> Self {
Self {
app_id: "aidens-app".into(),
provider: None,
provider_spec: ProviderSpecV1::new("disabled"),
tools: safe_coding_registry_for_current_dir(),
permit_policy: PermitPolicyV1::default(),
budget: BudgetV1::default(),
run_reports: RunReportLedger::default(),
receipt_level: ReportLevelV1::Full,
canonical_receipt_log_config: None,
agency_policy: AgencyPolicyEngineV1::default(),
agency_nudges: Arc::new(Mutex::new(NudgeLedgerV1::default())),
governance: None,
kernel: None,
}
}
}
impl AiDENsRunnerBuilder {
pub fn app_id(mut self, app_id: impl Into<String>) -> Self {
self.app_id = app_id.into();
self
}
pub fn provider(mut self, provider: Arc<dyn AiDENsProvider>) -> Self {
self.provider = Some(provider);
self
}
pub fn provider_spec(mut self, spec: ProviderSpecV1) -> Self {
self.provider_spec = spec;
self.provider = None;
self
}
pub fn provider_kind(mut self, kind: impl Into<String>) -> Self {
self.provider_spec.kind = kind.into();
self.provider = None;
self
}
pub fn mock_provider(mut self, response: impl Into<String>) -> Self {
self.provider_spec.kind = "mock".into();
self.provider_spec.mock_response = Some(response.into());
self.provider = None;
self
}
pub fn model(mut self, model: impl Into<String>) -> Self {
self.provider_spec.model = Some(model.into());
self.provider = None;
self
}
pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
self.provider_spec.base_url = Some(base_url.into());
self.provider = None;
self
}
pub fn api_key(mut self, api_key: impl Into<String>) -> Self {
self.provider_spec.api_key = Some(api_key.into());
self.provider = None;
self
}
pub fn tools(mut self, tools: ToolRegistryV1) -> Self {
self.tools = tools;
self
}
pub fn permit_policy(mut self, permit_policy: PermitPolicyV1) -> Self {
self.permit_policy = permit_policy;
self
}
pub fn budget(mut self, budget: BudgetV1) -> Self {
self.budget = budget;
self
}
pub fn run_reports(mut self, reports: RunReportLedger) -> Self {
self.run_reports = reports;
self
}
pub fn receipt_level(mut self, receipt_level: ReportLevelV1) -> Self {
self.receipt_level = receipt_level;
self
}
pub fn canonical_receipt_log_config(mut self, config: CanonicalEventLogConfig) -> Self {
self.canonical_receipt_log_config = Some(config);
self
}
pub fn agency_policy(mut self, policy: AgencyPolicyEngineV1) -> Self {
self.agency_policy = policy;
self
}
pub fn agency_nudge_ledger(mut self, ledger: Arc<Mutex<NudgeLedgerV1>>) -> Self {
self.agency_nudges = ledger;
self
}
pub fn governance(mut self, governance: Option<GovernanceContext>) -> Self {
self.governance = governance;
self
}
pub fn kernel(mut self, kernel: Option<CanonicalKernelAdapter>) -> Self {
self.kernel = kernel;
self
}
pub fn build(self) -> anyhow::Result<AiDENsRunner> {
let provider = match self.provider {
Some(provider) => provider,
None => build_provider(self.provider_spec)?,
};
let canonical_receipt_log_config = self
.canonical_receipt_log_config
.unwrap_or_else(|| default_runner_receipt_log_config(&self.app_id));
let canonical_receipts = Some(CanonicalEventLog::open(canonical_receipt_log_config)?);
Ok(AiDENsRunner {
app_id: self.app_id,
provider,
tools: self.tools,
permit_policy: self.permit_policy,
budget: self.budget,
run_reports: self.run_reports,
receipt_level: self.receipt_level,
canonical_receipts,
agency_policy: self.agency_policy,
agency_nudges: self.agency_nudges,
governance: self.governance,
kernel: self.kernel,
})
}
}
#[cfg(test)]
mod tests;