1use super::*;
2pub const KERNEL_ABI_VERSION: u32 = 2;
3pub const KERNEL_SNAPSHOT_VERSION: u32 = 2;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum KernelLifecycle {
8 Created,
9 Configured,
10 Running,
11 Suspended,
12 Completed,
13 Cancelled,
14 Failed,
15}
16
17impl KernelLifecycle {
18 pub fn is_terminal(self) -> bool {
19 matches!(self, Self::Completed | Self::Cancelled | Self::Failed)
20 }
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum PolicyAction {
29 Allow,
30 Deny,
31 AskUser,
32}
33
34impl From<PolicyAction> for crate::governance::permission::PermissionAction {
35 fn from(action: PolicyAction) -> Self {
36 match action {
37 PolicyAction::Allow => Self::Allow,
38 PolicyAction::Deny => Self::Deny,
39 PolicyAction::AskUser => Self::AskUser,
40 }
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct PolicyRule {
47 pub tool_pattern: String,
48 pub action: PolicyAction,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct RateLimitSpec {
55 pub tool: String,
56 pub max_calls: u32,
57 pub window_ms: u64,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(tag = "kind", rename_all = "snake_case")]
65pub enum ConstraintSpec {
66 Required { tool: String, path: String },
68 Enum {
70 tool: String,
71 path: String,
72 values: Vec<String>,
73 },
74 Range {
76 tool: String,
77 path: String,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 min: Option<f64>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 max: Option<f64>,
82 },
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct KernelInput {
87 pub version: u32,
88 pub operation_id: String,
89 pub event_id: String,
90 pub observed_at_ms: u64,
91 pub event: KernelInputEvent,
92}
93
94impl KernelInput {
95 pub fn new(event: KernelInputEvent) -> Self {
98 use std::sync::atomic::{AtomicU64, Ordering};
99 static LOCAL_EVENT_SEQUENCE: AtomicU64 = AtomicU64::new(1);
100 let event_seq = LOCAL_EVENT_SEQUENCE.fetch_add(1, Ordering::Relaxed);
101 Self::correlated(
102 "local-operation",
103 format!("local-event-{event_seq}"),
104 0,
105 event,
106 )
107 }
108
109 pub fn correlated(
110 operation_id: impl Into<String>,
111 event_id: impl Into<String>,
112 observed_at_ms: u64,
113 event: KernelInputEvent,
114 ) -> Self {
115 Self {
116 version: KERNEL_ABI_VERSION,
117 operation_id: operation_id.into(),
118 event_id: event_id.into(),
119 observed_at_ms,
120 event,
121 }
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(rename_all = "snake_case")]
128pub enum KernelPreparationStatus {
129 Prepared,
131 Replayed,
133 Rejected,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct KernelPreparedStep {
142 pub status: KernelPreparationStatus,
143 pub base_generation: u64,
145 #[serde(default, skip_serializing_if = "Option::is_none")]
146 pub prepare_token: Option<String>,
147 pub input: KernelInput,
148 pub step: KernelStep,
149}
150
151#[derive(Debug, Clone, Default, Serialize, Deserialize)]
154pub struct GovernanceConfig {
155 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub default_action: Option<PolicyAction>,
157 #[serde(default, skip_serializing_if = "Vec::is_empty")]
158 pub rules: Vec<PolicyRule>,
159 #[serde(default, skip_serializing_if = "Vec::is_empty")]
160 pub vetoed_tools: Vec<String>,
161 #[serde(default, skip_serializing_if = "Vec::is_empty")]
162 pub rate_limits: Vec<RateLimitSpec>,
163 #[serde(default, skip_serializing_if = "Vec::is_empty")]
164 pub constraints: Vec<ConstraintSpec>,
165}
166
167pub const SIGNAL_POLICY_VERSION: u32 = 1;
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
171#[serde(deny_unknown_fields)]
172pub struct SignalPolicyConfig {
173 pub version: u32,
174 pub queue_max: u32,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub ttl_ms: Option<u64>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub deadline_escalation: Option<bool>,
179}
180
181#[derive(Debug, Clone, Default, Serialize, Deserialize)]
184pub struct KernelReliabilityConfig {
185 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub event_replay_capacity: Option<usize>,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
188 pub completed_effect_replay_capacity: Option<usize>,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub provider_recovery_attempts: Option<u8>,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub output_recovery_attempts: Option<u8>,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub host_effect_retry_attempts: Option<u8>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub spool_threshold_bytes: Option<u32>,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub spool_preview_bytes: Option<u32>,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub snapshot_input_limit: Option<usize>,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
204 pub max_input_bytes: Option<usize>,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub snapshot_journal_bytes_limit: Option<usize>,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
213pub struct KernelDiagnostics {
214 pub lifecycle: KernelLifecycle,
215 pub next_step_seq: u64,
216 pub accepted_input_count: usize,
217 pub accepted_input_bytes: usize,
218 pub snapshot_input_limit: usize,
219 pub snapshot_journal_bytes_limit: usize,
220 pub max_input_bytes: usize,
221 pub snapshot_overflowed: bool,
222 pub recorded_event_count: usize,
223 pub completed_effect_count: usize,
224 pub pending_effect_count: usize,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct KernelSnapshot {
231 pub snapshot_version: u32,
232 pub abi_version: u32,
233 pub initial_policy: KernelSnapshotPolicy,
234 pub lifecycle: KernelLifecycle,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub operation_id: Option<String>,
237 pub next_step_seq: u64,
238 pub snapshot_input_limit: usize,
239 pub max_input_bytes: usize,
240 pub snapshot_journal_bytes_limit: usize,
241 pub accepted_input_bytes: usize,
242 #[serde(default)]
243 pub accepted_inputs: Vec<KernelInput>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub last_step: Option<KernelStep>,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
251pub struct KernelSnapshotPolicy {
252 pub max_tokens: u32,
253 pub max_turns: u32,
254 pub max_total_tokens: String,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub max_wall_ms: Option<String>,
257}
258
259impl From<&SchedulerBudget> for KernelSnapshotPolicy {
260 fn from(policy: &SchedulerBudget) -> Self {
261 Self {
262 max_tokens: policy.max_tokens,
263 max_turns: policy.max_turns,
264 max_total_tokens: policy.max_total_tokens.to_string(),
265 max_wall_ms: policy.max_wall_ms.map(|value| value.to_string()),
266 }
267 }
268}
269
270impl TryFrom<&KernelSnapshotPolicy> for SchedulerBudget {
271 type Error = String;
272
273 fn try_from(policy: &KernelSnapshotPolicy) -> Result<Self, Self::Error> {
274 Ok(Self {
275 max_tokens: policy.max_tokens,
276 max_turns: policy.max_turns,
277 max_total_tokens: policy.max_total_tokens.parse().map_err(|_| {
278 "snapshot max_total_tokens must be a u64 decimal string".to_string()
279 })?,
280 max_wall_ms: policy
281 .max_wall_ms
282 .as_deref()
283 .map(str::parse)
284 .transpose()
285 .map_err(|_| "snapshot max_wall_ms must be a u64 decimal string".to_string())?,
286 })
287 }
288}
289
290#[derive(Debug, Clone, Default, Serialize, Deserialize)]
294#[serde(deny_unknown_fields)]
295pub struct RunConfig {
296 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub reliability: Option<KernelReliabilityConfig>,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub tools: Option<Vec<ToolSchema>>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub available_skills: Option<Vec<SkillMetadata>>,
302 #[serde(default, skip_serializing_if = "Option::is_none")]
303 pub stable_core_tools: Option<Vec<String>>,
304 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub memory_enabled: Option<bool>,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub knowledge_enabled: Option<bool>,
308 #[serde(default, skip_serializing_if = "Option::is_none")]
309 pub plan_tool_enabled: Option<bool>,
310 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub tokenizer: Option<String>,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
314 pub governance: Option<GovernanceConfig>,
315 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub signal_policy: Option<SignalPolicyConfig>,
317 #[serde(default, skip_serializing_if = "Option::is_none")]
320 pub prompt_budget: Option<crate::context::config::PromptBudgetConfig>,
321 #[serde(default, skip_serializing_if = "Option::is_none")]
323 pub context_policy: Option<crate::context::policy::ContextPolicyV1>,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub scheduler_policy: Option<crate::scheduler::policy::SchedulerPolicyConfig>,
326 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub resource_quota: Option<crate::governance::quota::ResourceQuota>,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub budget_grant: Option<BudgetGrant>,
332 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub repeat_fuse: Option<crate::governance::repeat_fuse::RepeatFuseConfig>,
336 #[serde(default, skip_serializing_if = "Option::is_none")]
338 pub criteria_gate: Option<bool>,
339 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub knowledge_budget_ratio: Option<f64>,
343 #[serde(default, skip_serializing_if = "Option::is_none")]
347 pub entropy_watch: Option<crate::scheduler::entropy::EntropyWatchConfig>,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
351pub struct BudgetGrant {
352 pub reservation_id: String,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub tokens: Option<u64>,
355 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub subagents: Option<u32>,
357 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub rounds: Option<u32>,
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
362#[serde(rename_all = "snake_case")]
363pub enum CancellationReason {
364 User,
365 Deadline,
366 LeaseLost,
367 HostShutdown,
368}
369
370pub(crate) fn build_governance_pipeline(
374 default_action: Option<PolicyAction>,
375 rules: Vec<PolicyRule>,
376 vetoed_tools: Vec<String>,
377 rate_limits: Vec<RateLimitSpec>,
378 constraints: Vec<ConstraintSpec>,
379) -> crate::governance::pipeline::GovernancePipeline {
380 use crate::governance::constraint::{ConstraintRule, ParamConstraint};
381 use crate::governance::permission::PermissionRule;
382 use crate::governance::rate_limit::RateLimit;
383 let default = default_action.unwrap_or(PolicyAction::Allow).into();
384 let mut pipeline = crate::governance::pipeline::GovernancePipeline::new(default);
385 for rule in rules {
386 pipeline.permission.add_rule(PermissionRule {
387 tool_pattern: rule.tool_pattern.into(),
388 action: rule.action.into(),
389 });
390 }
391 for tool in vetoed_tools {
392 pipeline.veto.block_tool(tool);
393 }
394 for rl in rate_limits {
395 pipeline.rate_limiter.set_limit(
396 rl.tool,
397 RateLimit {
398 max_calls: rl.max_calls,
399 window_ms: rl.window_ms,
400 },
401 );
402 }
403 for c in constraints {
404 let (tool_name, param_path, rule) = match c {
405 ConstraintSpec::Required { tool, path } => (tool, path, ConstraintRule::Required),
406 ConstraintSpec::Enum { tool, path, values } => {
407 (tool, path, ConstraintRule::Enum(values))
408 }
409 ConstraintSpec::Range {
410 tool,
411 path,
412 min,
413 max,
414 } => (tool, path, ConstraintRule::Range { min, max }),
415 };
416 pipeline.constraints.add(ParamConstraint {
417 tool_name,
418 param_path,
419 rule,
420 });
421 }
422 pipeline
423}
424
425#[derive(Debug, Clone, Serialize, Deserialize)]
426#[serde(tag = "kind", rename_all = "snake_case")]
427pub enum KernelInputEvent {
428 SetTools {
429 tools: Vec<ToolSchema>,
430 },
431 SetAvailableSkills {
432 skills: Vec<SkillMetadata>,
433 },
434 SkillActivated {
438 name: String,
439 #[serde(default, skip_serializing_if = "Option::is_none")]
443 lease_turns: Option<u32>,
444 },
445 SkillDeactivated {
450 name: String,
451 },
452 SetStableCoreTools {
455 tool_ids: Vec<String>,
456 },
457 SetMemoryEnabled {
458 enabled: bool,
459 },
460 SetKnowledgeEnabled {
461 enabled: bool,
462 },
463 SetPlanToolEnabled {
464 enabled: bool,
465 },
466 SetTokenizer {
467 name: String,
468 },
469 AddSystemMessage {
470 content: String,
471 tokens: u32,
472 },
473 AddKnowledgeMessage {
474 content: String,
475 tokens: u32,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
479 key: Option<String>,
480 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
482 pinned: bool,
483 },
484 RemoveKnowledge {
487 key: String,
488 },
489 AddHistoryMessage {
490 message: Message,
491 tokens: Option<u32>,
492 },
493 PreloadHistory {
494 messages: Vec<Message>,
495 },
496 MountCapability {
497 capability: CapabilityDescriptor,
498 },
499 UnmountCapability {
500 capability_kind: CapabilityKind,
501 id: String,
502 },
503 LoadMilestoneContract {
504 contract: MilestoneContract,
505 },
506 LoadGovernancePolicy {
510 #[serde(default)]
511 default_action: Option<PolicyAction>,
512 #[serde(default, skip_serializing_if = "Vec::is_empty")]
513 rules: Vec<PolicyRule>,
514 #[serde(default, skip_serializing_if = "Vec::is_empty")]
515 vetoed_tools: Vec<String>,
516 #[serde(default, skip_serializing_if = "Vec::is_empty")]
519 rate_limits: Vec<RateLimitSpec>,
520 #[serde(default, skip_serializing_if = "Vec::is_empty")]
521 constraints: Vec<ConstraintSpec>,
522 },
523 SetSignalPolicy {
525 policy: SignalPolicyConfig,
526 },
527 ForceCompact,
528 UpdateTask {
529 update: TaskUpdate,
530 },
531 StartRun {
532 task: RuntimeTask,
533 #[serde(default, skip_serializing_if = "Option::is_none")]
534 run_spec: Option<AgentRunSpec>,
535 },
536 ConfigureRun {
540 config: RunConfig,
541 },
542 CapabilityCommand {
543 command: CapabilityCommand,
544 },
545 Resume,
548 ApprovalResult {
549 effect_id: String,
550 #[serde(default, skip_serializing_if = "Vec::is_empty")]
551 approved_calls: Vec<String>,
552 #[serde(default, skip_serializing_if = "Vec::is_empty")]
553 denied_calls: Vec<String>,
554 #[serde(default, skip_serializing_if = "Option::is_none")]
555 error: Option<String>,
556 },
557 WorkflowSpawnResult {
560 effect_id: String,
561 #[serde(default, skip_serializing_if = "Vec::is_empty")]
562 started_agent_ids: Vec<String>,
563 #[serde(default, skip_serializing_if = "Vec::is_empty")]
564 failures: Vec<WorkflowSpawnFailure>,
565 #[serde(default, skip_serializing_if = "Option::is_none")]
566 error: Option<String>,
567 },
568 PreemptResult {
569 effect_id: String,
570 #[serde(default, skip_serializing_if = "Option::is_none")]
571 error: Option<String>,
572 },
573 MemoryPersistResult {
574 effect_id: String,
575 #[serde(default, skip_serializing_if = "Option::is_none")]
576 error: Option<String>,
577 },
578 MemoryQueryResult {
579 effect_id: String,
580 #[serde(default, skip_serializing_if = "Vec::is_empty")]
581 hits: Vec<crate::mm::memory::MemoryRecall>,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
583 error: Option<String>,
584 },
585 LargeResultSpoolResult {
586 effect_id: String,
587 #[serde(default, skip_serializing_if = "Option::is_none")]
588 spool_ref: Option<String>,
589 #[serde(default, skip_serializing_if = "Option::is_none")]
590 error: Option<String>,
591 },
592 PageOutArchiveResult {
593 effect_id: String,
594 #[serde(default, skip_serializing_if = "Option::is_none")]
595 archive_ref: Option<String>,
596 #[serde(default, skip_serializing_if = "Option::is_none")]
597 error: Option<String>,
598 },
599 SetKnowledgeBudget {
602 ratio: f64,
603 },
604 SetCriteriaGate {
607 enabled: bool,
608 },
609 SetRepeatFuse {
612 #[serde(default, skip_serializing_if = "Option::is_none")]
613 enabled: Option<bool>,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 deny_after: Option<u32>,
616 #[serde(default, skip_serializing_if = "Option::is_none")]
617 terminate_after: Option<u32>,
618 },
619 SetEntropyWatch {
624 #[serde(default, skip_serializing_if = "Option::is_none")]
625 enabled: Option<bool>,
626 #[serde(default, skip_serializing_if = "Option::is_none")]
627 threshold: Option<f64>,
628 #[serde(default, skip_serializing_if = "Option::is_none")]
629 hysteresis: Option<f64>,
630 #[serde(default, skip_serializing_if = "Option::is_none")]
631 cooldown_turns: Option<u32>,
632 #[serde(default, skip_serializing_if = "Option::is_none")]
633 notify_model: Option<bool>,
634 },
635 SetSchedulerBudget {
639 #[serde(default, skip_serializing_if = "Option::is_none")]
640 max_wall_ms: Option<u64>,
641 },
642 SetResourceQuota {
648 quota: crate::governance::quota::ResourceQuota,
649 },
650 ProviderResult {
651 effect_id: String,
652 message: Message,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
654 observed_input_tokens: Option<u32>,
655 #[serde(default, skip_serializing_if = "Option::is_none")]
656 observed_output_tokens: Option<u32>,
657 #[serde(default, skip_serializing_if = "Option::is_none")]
661 now_ms: Option<u64>,
662 #[serde(default, skip_serializing_if = "Option::is_none")]
666 stop_reason: Option<String>,
667 },
668 ToolResults {
669 effect_id: String,
670 results: Vec<ToolResult>,
671 },
672 ProviderError {
679 effect_id: String,
680 message: String,
681 },
682 DeliverSignal {
683 delivery_id: String,
684 attempt: u32,
685 signal: RuntimeSignal,
686 },
687 MilestoneResult {
688 effect_id: String,
689 result: MilestoneCheckResult,
690 },
691 SpawnSubAgent {
693 spec: AgentRunSpec,
694 parent_session_id: String,
695 },
696 LoadWorkflow {
701 spec: crate::orchestration::workflow::WorkflowSpec,
702 parent_session_id: String,
703 #[serde(default, skip_serializing_if = "Vec::is_empty")]
707 resumed_submissions: Vec<Vec<crate::orchestration::workflow::WorkflowNode>>,
708 #[serde(default)]
711 resumed_submission_bases: Vec<u32>,
712 #[serde(default, skip_serializing_if = "Vec::is_empty")]
715 resumed_outcomes: Vec<crate::orchestration::workflow::ResumedNodeOutcome>,
716 },
717 SubAgentCompleted {
719 result: SubAgentResult,
720 },
721 SubmitWorkflowNodes {
726 #[serde(default, skip_serializing_if = "Vec::is_empty")]
727 nodes: Vec<crate::orchestration::workflow::WorkflowNode>,
728 #[serde(default, skip_serializing_if = "Option::is_none")]
732 submitter_agent_id: Option<String>,
733 },
734 SubmitWorkflow {
740 spec: crate::orchestration::workflow::WorkflowSpec,
741 #[serde(default)]
743 parent_session_id: String,
744 #[serde(default, skip_serializing_if = "Option::is_none")]
747 submitter_agent_id: Option<String>,
748 },
749 PageIn {
752 #[serde(default, skip_serializing_if = "Vec::is_empty")]
753 entries: Vec<crate::mm::PageInEntry>,
754 },
755 SetMemoryPolicy {
758 #[serde(default)]
759 memory_path: String,
760 #[serde(default = "default_stale_days")]
761 stale_warning_days: u32,
762 #[serde(default = "default_top_k")]
763 retrieval_top_k: usize,
764 #[serde(default = "default_validation_enabled")]
765 validation_enabled: bool,
766 #[serde(default, skip_serializing_if = "Option::is_none")]
768 max_content_bytes: Option<u32>,
769 #[serde(default, skip_serializing_if = "Option::is_none")]
771 max_name_length: Option<usize>,
772 #[serde(default, skip_serializing_if = "Option::is_none")]
774 promotion_recall_threshold: Option<u64>,
775 },
776 WriteMemory {
778 memory: crate::mm::memory::MemoryRecord,
779 },
780 QueryMemory {
782 query: crate::mm::memory::MemoryQuery,
783 },
784 CompleteRun,
788 CancelOperation {
791 operation_id: String,
792 reason: CancellationReason,
793 #[serde(default, skip_serializing_if = "Vec::is_empty")]
794 pending_call_ids: Vec<String>,
795 },
796}
797
798fn default_stale_days() -> u32 {
799 2
800}
801fn default_top_k() -> usize {
802 5
803}
804fn default_validation_enabled() -> bool {
805 true
806}
807
808#[derive(Debug, Clone, Serialize, Deserialize)]
809pub struct KernelStep {
810 pub version: u32,
811 pub operation_id: String,
812 pub input_event_id: String,
813 pub step_seq: u64,
814 pub actions: Vec<KernelAction>,
815 pub observations: Vec<KernelObservation>,
816 #[serde(default, skip_serializing_if = "Vec::is_empty")]
817 pub faults: Vec<KernelFault>,
818}
819
820impl KernelStep {
821 pub(super) fn empty(
822 operation_id: String,
823 input_event_id: String,
824 step_seq: u64,
825 observations: Vec<KernelObservation>,
826 ) -> Self {
827 Self {
828 version: KERNEL_ABI_VERSION,
829 operation_id,
830 input_event_id,
831 step_seq,
832 actions: Vec::new(),
833 observations,
834 faults: Vec::new(),
835 }
836 }
837
838 pub(super) fn single(
839 operation_id: String,
840 input_event_id: String,
841 step_seq: u64,
842 action: LoopAction,
843 observations: Vec<KernelObservation>,
844 ) -> Self {
845 let effect_id = format!("{operation_id}:step:{step_seq}:effect:0");
846 Self {
847 version: KERNEL_ABI_VERSION,
848 operation_id,
849 input_event_id: input_event_id.clone(),
850 step_seq,
851 actions: vec![KernelAction::from_loop(effect_id, input_event_id, action)],
852 observations,
853 faults: Vec::new(),
854 }
855 }
856
857 pub(super) fn fault(
858 operation_id: String,
859 input_event_id: String,
860 step_seq: u64,
861 fault: KernelFault,
862 ) -> Self {
863 Self {
864 version: KERNEL_ABI_VERSION,
865 operation_id,
866 input_event_id,
867 step_seq,
868 actions: Vec::new(),
869 observations: Vec::new(),
870 faults: vec![fault],
871 }
872 }
873}
874
875#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
876#[serde(rename_all = "snake_case")]
877pub enum KernelFaultCode {
878 VersionMismatch,
879 OperationMismatch,
880 InvalidLifecycle,
881 InvalidConfig,
882 ResourceLimitExceeded,
883 DuplicateEventConflict,
884 UnexpectedEffectResult,
885 TransactionConflict,
886 SnapshotIncompatible,
887}
888
889#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
890pub struct KernelFault {
891 pub code: KernelFaultCode,
892 pub message: String,
893 #[serde(default, skip_serializing_if = "Option::is_none")]
894 pub operation_id: Option<String>,
895 #[serde(default, skip_serializing_if = "Option::is_none")]
896 pub event_id: Option<String>,
897 #[serde(default, skip_serializing_if = "Option::is_none")]
898 pub effect_id: Option<String>,
899}
900
901#[derive(Debug, Clone, Serialize, Deserialize)]
902pub struct KernelAction {
903 pub effect_id: String,
904 pub causation_id: String,
905 #[serde(flatten)]
906 pub effect: KernelEffect,
907}
908
909impl KernelAction {
910 fn from_loop(effect_id: String, causation_id: String, action: LoopAction) -> Self {
911 Self {
912 effect_id,
913 causation_id,
914 effect: action.into(),
915 }
916 }
917}
918
919#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
920pub struct WorkflowSpawnFailure {
921 pub agent_id: String,
922 pub error: String,
923}
924
925#[derive(Debug, Clone, Serialize, Deserialize)]
926#[serde(tag = "kind", rename_all = "snake_case")]
927pub enum KernelEffect {
928 CallProvider {
929 context: RenderedContext,
930 tools: Vec<ToolSchema>,
931 },
932 ExecuteTool {
933 calls: Vec<ToolCall>,
934 },
935 RequestApproval {
936 requests: Vec<crate::scheduler::state_machine::ApprovalRequest>,
937 },
938 SpawnWorkflow {
939 nodes: Vec<crate::orchestration::workflow::WorkflowSpawnInfo>,
940 #[serde(default, skip_serializing_if = "Option::is_none")]
941 budget: Option<crate::orchestration::workflow::WorkflowBudget>,
942 },
943 PreemptSubAgents {
944 agent_ids: Vec<String>,
945 reason: String,
946 },
947 PersistMemory {
948 memory: crate::mm::memory::MemoryRecord,
949 },
950 QueryMemory {
951 query: crate::mm::memory::MemoryQuery,
952 requested_k: usize,
953 },
954 SpoolLargeResult {
955 call_id: String,
956 tool: String,
957 output: String,
958 original_size: u32,
959 preview_size: u32,
960 },
961 ArchivePageOut {
962 turn: u32,
963 action: KernelPressureAction,
964 summary: Option<String>,
965 archived: Vec<Message>,
966 tier: String,
967 },
968 EvaluateMilestone {
969 phase_id: String,
970 criteria: Vec<String>,
971 #[serde(default, skip_serializing_if = "Option::is_none")]
972 verifier: Option<crate::types::milestone::MilestoneVerifier>,
973 #[serde(default, skip_serializing_if = "Vec::is_empty")]
974 required_evidence: Vec<String>,
975 },
976 Done {
977 result: LoopResult,
978 },
979}
980
981impl From<LoopAction> for KernelEffect {
982 fn from(action: LoopAction) -> Self {
983 match action {
984 LoopAction::AwaitingResume => {
985 panic!("AwaitingResume must not be converted to KernelEffect")
986 }
987 LoopAction::CallLLM { context, tools } => Self::CallProvider { context, tools },
988 LoopAction::ExecuteTools { calls } => Self::ExecuteTool { calls },
989 LoopAction::RequestApproval { requests } => Self::RequestApproval { requests },
990 LoopAction::SpawnWorkflow { nodes, budget } => Self::SpawnWorkflow { nodes, budget },
991 LoopAction::PreemptSubAgents { agent_ids, reason } => {
992 Self::PreemptSubAgents { agent_ids, reason }
993 }
994 LoopAction::PersistMemory { memory } => Self::PersistMemory { memory },
995 LoopAction::QueryMemory { query, requested_k } => {
996 Self::QueryMemory { query, requested_k }
997 }
998 LoopAction::SpoolLargeResult {
999 call_id,
1000 tool,
1001 output,
1002 original_size,
1003 preview_size,
1004 } => Self::SpoolLargeResult {
1005 call_id,
1006 tool,
1007 output,
1008 original_size,
1009 preview_size,
1010 },
1011 LoopAction::ArchivePageOut {
1012 turn,
1013 action,
1014 summary,
1015 archived,
1016 tier,
1017 } => Self::ArchivePageOut {
1018 turn,
1019 action,
1020 summary,
1021 archived,
1022 tier,
1023 },
1024 LoopAction::EvaluateMilestone {
1025 phase_id,
1026 criteria,
1027 verifier,
1028 required_evidence,
1029 } => Self::EvaluateMilestone {
1030 phase_id,
1031 criteria,
1032 verifier,
1033 required_evidence,
1034 },
1035 LoopAction::Done { result } => Self::Done { result },
1036 }
1037 }
1038}
1039
1040#[derive(Debug, Clone, Serialize, Deserialize)]
1041#[serde(tag = "kind", rename_all = "snake_case")]
1042pub enum KernelObservation {
1043 Compressed {
1046 #[serde(default)]
1047 turn: u32,
1048 action: KernelPressureAction,
1049 rho_after: f64,
1050 summary: Option<String>,
1051 archived_count: u32,
1052 #[serde(default, skip_serializing_if = "Option::is_none")]
1056 invalidates_prefix_at: Option<usize>,
1057 },
1058 Renewed {
1059 sprint: u32,
1060 },
1061 ContextBudgetExceeded {
1064 turn: u32,
1065 overflow_kind: crate::context::renderer::ContextBudgetOverflowKind,
1066 required_tokens: u32,
1067 max_tokens: u32,
1068 },
1069 KnowledgeSwept {
1073 turn: u32,
1074 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1075 removed_keys: Vec<String>,
1076 tokens_freed: u32,
1077 },
1078 KnowledgeBudgetExceeded {
1082 turn: u32,
1083 used: u32,
1084 budget: u32,
1085 },
1086 Rollbacked {
1087 turn: u32,
1088 checkpoint_history_len: u32,
1089 #[serde(default, skip_serializing_if = "Option::is_none")]
1090 reason: Option<RollbackReason>,
1091 },
1092 CapabilityChanged {
1093 turn: u32,
1094 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1095 added: Vec<String>,
1096 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1097 removed: Vec<String>,
1098 #[serde(default, skip_serializing_if = "Option::is_none")]
1099 change_kind: Option<String>,
1100 #[serde(default, skip_serializing_if = "Option::is_none")]
1101 capability_id: Option<String>,
1102 #[serde(default, skip_serializing_if = "Option::is_none")]
1103 version: Option<String>,
1104 #[serde(default, skip_serializing_if = "Option::is_none")]
1105 mounted_by: Option<String>,
1106 #[serde(default, skip_serializing_if = "Option::is_none")]
1107 mount_reason: Option<String>,
1108 },
1109 MilestoneAdvanced {
1110 turn: u32,
1111 phase_id: String,
1112 capabilities_unlocked: Vec<String>,
1113 },
1114 MilestoneBlocked {
1115 turn: u32,
1116 phase_id: String,
1117 reason: String,
1118 },
1119 CheckpointTaken {
1121 turn: u32,
1122 history_len: u32,
1123 },
1124 RepeatFuseTripped {
1128 turn: u32,
1129 signature: String,
1130 count: u32,
1131 action: String,
1132 },
1133 CriteriaGateFired {
1136 turn: u32,
1137 criteria: Vec<String>,
1138 },
1139 EntropySample {
1144 turn: u32,
1145 score: f64,
1146 score_version: u32,
1147 rho: f64,
1148 repeat_pressure: f64,
1149 failure_rate: f64,
1150 rollbacks_in_window: u32,
1151 window_turns: u32,
1152 },
1153 EntropyAlert {
1157 turn: u32,
1158 score: f64,
1159 threshold: f64,
1160 },
1161 AgentProcessChanged {
1163 turn: u32,
1164 agent_id: String,
1165 parent_session_id: String,
1166 role: String,
1167 isolation: String,
1168 context_inheritance: String,
1169 state: String,
1170 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1171 permitted_capability_ids: Vec<String>,
1172 #[serde(default, skip_serializing_if = "Option::is_none")]
1173 result_termination: Option<String>,
1174 },
1175 WorkflowBatchSpawned {
1178 turn: u32,
1179 nodes: Vec<crate::orchestration::workflow::WorkflowSpawnInfo>,
1180 #[serde(default, skip_serializing_if = "Option::is_none")]
1184 budget: Option<crate::orchestration::workflow::WorkflowBudget>,
1185 },
1186 WorkflowSpawnFailed {
1189 turn: u32,
1190 error: String,
1191 },
1192 WorkflowCompleted {
1194 turn: u32,
1195 node_outcomes: Vec<crate::orchestration::workflow::run::WorkflowNodeOutcome>,
1196 },
1197 AgentPreempted {
1203 turn: u32,
1204 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1205 agent_ids: Vec<String>,
1206 reason: String,
1207 },
1208 AgentPreemptFailed {
1209 turn: u32,
1210 agent_ids: Vec<String>,
1211 reason: String,
1212 error: String,
1213 },
1214 RoundPaced {
1216 turn: u32,
1217 round: u32,
1218 decision: crate::types::result::PaceDecision,
1219 },
1220 WorkflowNodesSubmitted {
1225 turn: u32,
1226 base: u32,
1227 count: u32,
1228 #[serde(default, skip_serializing_if = "Option::is_none")]
1231 submitter: Option<String>,
1232 },
1233 NodesRejected {
1235 turn: u32,
1236 node_index: u32,
1237 reason: String,
1238 },
1239 ToolGated {
1242 turn: u32,
1243 call_id: String,
1244 tool: String,
1245 reason: String,
1246 },
1247 SignalDeliveryDisposed {
1249 turn: u32,
1250 operation_id: String,
1251 delivery_id: String,
1252 attempt: u32,
1253 signal_id: String,
1254 disposition: String,
1255 queue_depth: u32,
1256 },
1257 SignalDisplaced {
1258 turn: u32,
1259 admitted_signal_id: String,
1260 displaced_signal_id: String,
1261 queue_depth: u32,
1262 },
1263 SignalExpired {
1264 turn: u32,
1265 signal_id: String,
1266 queue_depth: u32,
1267 },
1268 SignalsPending {
1269 turn: u32,
1270 depth: u32,
1271 },
1272 BudgetExceeded {
1274 turn: u32,
1275 budget: String,
1276 operation_id: String,
1277 #[serde(default, skip_serializing_if = "Option::is_none")]
1278 reservation_id: Option<String>,
1279 },
1280 BudgetUsageReported {
1282 operation_id: String,
1283 reservation_id: String,
1284 tokens: u64,
1285 subagents: u32,
1286 rounds: u32,
1287 },
1288 OperationCancelled {
1290 turn: u32,
1291 operation_id: String,
1292 reason: CancellationReason,
1293 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1294 pending_call_ids: Vec<String>,
1295 },
1296 Suspended {
1298 turn: u32,
1299 reason: String,
1300 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1301 pending_calls: Vec<String>,
1302 },
1303 Resumed {
1305 turn: u32,
1306 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1307 approved: Vec<String>,
1308 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1309 denied: Vec<String>,
1310 },
1311 ApprovalResolutionFailed {
1312 turn: u32,
1313 error: String,
1314 },
1315 MemoryWritten {
1317 turn: u32,
1318 record_id: String,
1319 scope: crate::mm::memory::MemoryScope,
1320 memory_kind: crate::mm::memory::MemoryKind,
1321 name: String,
1322 size_bytes: u32,
1323 },
1324 MemoryValidationFailed {
1326 turn: u32,
1327 record_id: String,
1328 error: String,
1329 },
1330 MemoryWriteFailed {
1331 turn: u32,
1332 record_id: String,
1333 error: String,
1334 },
1335 MemoryQueried {
1337 turn: u32,
1338 scope: crate::mm::memory::MemoryScope,
1339 query: String,
1340 requested_k: usize,
1341 requires_async_response: bool,
1342 },
1343 MemoryQueryFailed {
1344 turn: u32,
1345 scope: crate::mm::memory::MemoryScope,
1346 query: String,
1347 error: String,
1348 },
1349 MemoryRecalled {
1353 turn: u32,
1354 scope: crate::mm::memory::MemoryScope,
1355 recalls: Vec<crate::mm::memory::MemoryRecallLifecycle>,
1356 },
1357 PromotionSuggested {
1360 turn: u32,
1361 record_id: String,
1362 recall_count: u64,
1363 },
1364 LargeResultSpooled {
1366 turn: u32,
1367 call_id: String,
1368 tool: String,
1369 original_size: u32,
1370 preview_size: u32,
1371 spool_ref: Option<String>,
1372 },
1373 LargeResultSpoolFailed {
1374 turn: u32,
1375 call_id: String,
1376 tool: String,
1377 error: String,
1378 },
1379 PageOutArchived {
1380 turn: u32,
1381 action: KernelPressureAction,
1382 summary: Option<String>,
1383 tier: String,
1384 message_count: u32,
1385 #[serde(default, skip_serializing_if = "Option::is_none")]
1386 archive_ref: Option<String>,
1387 },
1388 PageOutArchiveFailed {
1389 turn: u32,
1390 action: KernelPressureAction,
1391 tier: String,
1392 message_count: u32,
1393 error: String,
1394 },
1395}
1396
1397#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1398#[serde(rename_all = "snake_case")]
1399pub enum KernelPressureAction {
1400 None,
1401 SnipCompact,
1402 MicroCompact,
1403 ContextCollapse,
1404 AutoCompact,
1405}
1406
1407impl From<PressureAction> for KernelPressureAction {
1408 fn from(action: PressureAction) -> Self {
1409 match action {
1410 PressureAction::None => Self::None,
1411 PressureAction::SnipCompact => Self::SnipCompact,
1412 PressureAction::MicroCompact => Self::MicroCompact,
1413 PressureAction::ContextCollapse => Self::ContextCollapse,
1414 PressureAction::AutoCompact => Self::AutoCompact,
1415 }
1416 }
1417}