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 const LOCAL_OPERATION_ID: &'static str = "local-operation";
101
102 pub fn new(event: KernelInputEvent) -> Self {
105 use std::sync::atomic::{AtomicU64, Ordering};
106 static LOCAL_EVENT_SEQUENCE: AtomicU64 = AtomicU64::new(1);
107 let event_seq = LOCAL_EVENT_SEQUENCE.fetch_add(1, Ordering::Relaxed);
108 Self::correlated(
109 Self::LOCAL_OPERATION_ID,
110 format!("local-event-{event_seq}"),
111 0,
112 event,
113 )
114 }
115
116 pub fn correlated(
117 operation_id: impl Into<String>,
118 event_id: impl Into<String>,
119 observed_at_ms: u64,
120 event: KernelInputEvent,
121 ) -> Self {
122 Self {
123 version: KERNEL_ABI_VERSION,
124 operation_id: operation_id.into(),
125 event_id: event_id.into(),
126 observed_at_ms,
127 event,
128 }
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "snake_case")]
135pub enum KernelPreparationStatus {
136 Prepared,
138 Replayed,
140 Rejected,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct KernelPreparedStep {
149 pub status: KernelPreparationStatus,
150 pub base_generation: u64,
152 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub prepare_token: Option<String>,
154 pub input: KernelInput,
155 pub step: KernelStep,
156}
157
158#[derive(Debug, Clone, Default, Serialize, Deserialize)]
161pub struct GovernanceConfig {
162 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub default_action: Option<PolicyAction>,
164 #[serde(default, skip_serializing_if = "Vec::is_empty")]
165 pub rules: Vec<PolicyRule>,
166 #[serde(default, skip_serializing_if = "Vec::is_empty")]
167 pub vetoed_tools: Vec<String>,
168 #[serde(default, skip_serializing_if = "Vec::is_empty")]
169 pub rate_limits: Vec<RateLimitSpec>,
170 #[serde(default, skip_serializing_if = "Vec::is_empty")]
171 pub constraints: Vec<ConstraintSpec>,
172}
173
174pub const SIGNAL_POLICY_VERSION: u32 = 1;
175
176#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
178#[serde(deny_unknown_fields)]
179pub struct SignalPolicyConfig {
180 pub version: u32,
181 pub queue_max: u32,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub ttl_ms: Option<u64>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub deadline_escalation: Option<bool>,
186}
187
188#[derive(Debug, Clone, Default, Serialize, Deserialize)]
191pub struct KernelReliabilityConfig {
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub event_replay_capacity: Option<usize>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub completed_effect_replay_capacity: Option<usize>,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub provider_recovery_attempts: Option<u8>,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub output_recovery_attempts: Option<u8>,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub host_effect_retry_attempts: Option<u8>,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
203 pub spool_threshold_bytes: Option<u32>,
204 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub spool_preview_bytes: Option<u32>,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub snapshot_input_limit: Option<usize>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub max_input_bytes: Option<usize>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub snapshot_journal_bytes_limit: Option<usize>,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
220pub struct KernelDiagnostics {
221 pub lifecycle: KernelLifecycle,
222 pub next_step_seq: u64,
223 pub accepted_input_count: usize,
224 pub accepted_input_bytes: usize,
225 pub snapshot_input_limit: usize,
226 pub snapshot_journal_bytes_limit: usize,
227 pub max_input_bytes: usize,
228 pub snapshot_overflowed: bool,
229 pub recorded_event_count: usize,
230 pub completed_effect_count: usize,
231 pub pending_effect_count: usize,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct KernelSnapshot {
238 pub snapshot_version: u32,
239 pub abi_version: u32,
240 pub initial_policy: KernelSnapshotPolicy,
241 pub lifecycle: KernelLifecycle,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub operation_id: Option<String>,
244 pub next_step_seq: u64,
245 pub snapshot_input_limit: usize,
246 pub max_input_bytes: usize,
247 pub snapshot_journal_bytes_limit: usize,
248 pub accepted_input_bytes: usize,
249 #[serde(default)]
250 pub accepted_inputs: Vec<KernelInput>,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub last_step: Option<KernelStep>,
253}
254
255#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
258pub struct KernelSnapshotPolicy {
259 pub max_tokens: u32,
260 pub max_turns: u32,
261 pub max_total_tokens: String,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
263 pub max_wall_ms: Option<String>,
264}
265
266impl From<&SchedulerBudget> for KernelSnapshotPolicy {
267 fn from(policy: &SchedulerBudget) -> Self {
268 Self {
269 max_tokens: policy.max_tokens,
270 max_turns: policy.max_turns,
271 max_total_tokens: policy.max_total_tokens.to_string(),
272 max_wall_ms: policy.max_wall_ms.map(|value| value.to_string()),
273 }
274 }
275}
276
277impl TryFrom<&KernelSnapshotPolicy> for SchedulerBudget {
278 type Error = String;
279
280 fn try_from(policy: &KernelSnapshotPolicy) -> Result<Self, Self::Error> {
281 Ok(Self {
282 max_tokens: policy.max_tokens,
283 max_turns: policy.max_turns,
284 max_total_tokens: policy.max_total_tokens.parse().map_err(|_| {
285 "snapshot max_total_tokens must be a u64 decimal string".to_string()
286 })?,
287 max_wall_ms: policy
288 .max_wall_ms
289 .as_deref()
290 .map(str::parse)
291 .transpose()
292 .map_err(|_| "snapshot max_wall_ms must be a u64 decimal string".to_string())?,
293 })
294 }
295}
296
297#[derive(Debug, Clone, Default, Serialize, Deserialize)]
301#[serde(deny_unknown_fields)]
302pub struct RunConfig {
303 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub reliability: Option<KernelReliabilityConfig>,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
306 pub tools: Option<Vec<ToolSchema>>,
307 #[serde(default, skip_serializing_if = "Option::is_none")]
308 pub available_skills: Option<Vec<SkillMetadata>>,
309 #[serde(default, skip_serializing_if = "Option::is_none")]
310 pub stable_core_tools: Option<Vec<String>>,
311 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub memory_enabled: Option<bool>,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
314 pub knowledge_enabled: Option<bool>,
315 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub plan_tool_enabled: Option<bool>,
317 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub tokenizer: Option<String>,
320 #[serde(default, skip_serializing_if = "Option::is_none")]
321 pub governance: Option<GovernanceConfig>,
322 #[serde(default, skip_serializing_if = "Option::is_none")]
323 pub signal_policy: Option<SignalPolicyConfig>,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub prompt_budget: Option<crate::context::config::PromptBudgetConfig>,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
330 pub context_policy: Option<crate::context::policy::ContextPolicyV1>,
331 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub scheduler_policy: Option<crate::scheduler::policy::SchedulerPolicyConfig>,
333 #[serde(default, skip_serializing_if = "Option::is_none")]
334 pub resource_quota: Option<crate::governance::quota::ResourceQuota>,
335 #[serde(default, skip_serializing_if = "Option::is_none")]
338 pub budget_grant: Option<BudgetGrant>,
339 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub repeat_fuse: Option<crate::governance::repeat_fuse::RepeatFuseConfig>,
343 #[serde(default, skip_serializing_if = "Option::is_none")]
345 pub criteria_gate: Option<bool>,
346 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub knowledge_budget_ratio: Option<f64>,
350 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub entropy_watch: Option<crate::scheduler::entropy::EntropyWatchConfig>,
355}
356
357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
358pub struct BudgetGrant {
359 pub reservation_id: String,
360 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub tokens: Option<u64>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub subagents: Option<u32>,
364 #[serde(default, skip_serializing_if = "Option::is_none")]
365 pub rounds: Option<u32>,
366}
367
368#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
369#[serde(rename_all = "snake_case")]
370pub enum CancellationReason {
371 User,
372 Deadline,
373 LeaseLost,
374 HostShutdown,
375}
376
377pub(crate) fn build_governance_pipeline(
381 default_action: Option<PolicyAction>,
382 rules: Vec<PolicyRule>,
383 vetoed_tools: Vec<String>,
384 rate_limits: Vec<RateLimitSpec>,
385 constraints: Vec<ConstraintSpec>,
386) -> crate::governance::pipeline::GovernancePipeline {
387 use crate::governance::constraint::{ConstraintRule, ParamConstraint};
388 use crate::governance::permission::PermissionRule;
389 use crate::governance::rate_limit::RateLimit;
390 let default = default_action.unwrap_or(PolicyAction::Allow).into();
391 let mut pipeline = crate::governance::pipeline::GovernancePipeline::new(default);
392 for rule in rules {
393 pipeline.permission.add_rule(PermissionRule {
394 tool_pattern: rule.tool_pattern.into(),
395 action: rule.action.into(),
396 });
397 }
398 for tool in vetoed_tools {
399 pipeline.veto.block_tool(tool);
400 }
401 for rl in rate_limits {
402 pipeline.rate_limiter.set_limit(
403 rl.tool,
404 RateLimit {
405 max_calls: rl.max_calls,
406 window_ms: rl.window_ms,
407 },
408 );
409 }
410 for c in constraints {
411 let (tool_name, param_path, rule) = match c {
412 ConstraintSpec::Required { tool, path } => (tool, path, ConstraintRule::Required),
413 ConstraintSpec::Enum { tool, path, values } => {
414 (tool, path, ConstraintRule::Enum(values))
415 }
416 ConstraintSpec::Range {
417 tool,
418 path,
419 min,
420 max,
421 } => (tool, path, ConstraintRule::Range { min, max }),
422 };
423 pipeline.constraints.add(ParamConstraint {
424 tool_name,
425 param_path,
426 rule,
427 });
428 }
429 pipeline
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
433#[serde(tag = "kind", rename_all = "snake_case")]
434pub enum KernelInputEvent {
435 SetTools {
436 tools: Vec<ToolSchema>,
437 },
438 SetAvailableSkills {
439 skills: Vec<SkillMetadata>,
440 },
441 SkillActivated {
445 name: String,
446 #[serde(default, skip_serializing_if = "Option::is_none")]
450 lease_turns: Option<u32>,
451 },
452 SkillDeactivated {
457 name: String,
458 },
459 SetStableCoreTools {
462 tool_ids: Vec<String>,
463 },
464 SetMemoryEnabled {
465 enabled: bool,
466 },
467 SetKnowledgeEnabled {
468 enabled: bool,
469 },
470 SetPlanToolEnabled {
471 enabled: bool,
472 },
473 SetTokenizer {
474 name: String,
475 },
476 AddSystemMessage {
477 content: String,
478 tokens: u32,
479 },
480 AddKnowledgeMessage {
481 content: String,
482 tokens: u32,
483 #[serde(default, skip_serializing_if = "Option::is_none")]
486 key: Option<String>,
487 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
489 pinned: bool,
490 },
491 RemoveKnowledge {
494 key: String,
495 },
496 AddHistoryMessage {
497 message: Message,
498 tokens: Option<u32>,
499 },
500 PreloadHistory {
501 messages: Vec<Message>,
502 },
503 MountCapability {
504 capability: CapabilityDescriptor,
505 },
506 UnmountCapability {
507 capability_kind: CapabilityKind,
508 id: String,
509 },
510 LoadMilestoneContract {
511 contract: MilestoneContract,
512 },
513 LoadGovernancePolicy {
517 #[serde(default)]
518 default_action: Option<PolicyAction>,
519 #[serde(default, skip_serializing_if = "Vec::is_empty")]
520 rules: Vec<PolicyRule>,
521 #[serde(default, skip_serializing_if = "Vec::is_empty")]
522 vetoed_tools: Vec<String>,
523 #[serde(default, skip_serializing_if = "Vec::is_empty")]
526 rate_limits: Vec<RateLimitSpec>,
527 #[serde(default, skip_serializing_if = "Vec::is_empty")]
528 constraints: Vec<ConstraintSpec>,
529 },
530 SetSignalPolicy {
532 policy: SignalPolicyConfig,
533 },
534 ForceCompact,
535 UpdateTask {
536 update: TaskUpdate,
537 },
538 StartRun {
539 task: RuntimeTask,
540 #[serde(default, skip_serializing_if = "Option::is_none")]
541 run_spec: Option<AgentRunSpec>,
542 },
543 ConfigureRun {
547 config: RunConfig,
548 },
549 CapabilityCommand {
550 command: CapabilityCommand,
551 },
552 Resume,
555 ApprovalResult {
556 effect_id: String,
557 #[serde(default, skip_serializing_if = "Vec::is_empty")]
558 approved_calls: Vec<String>,
559 #[serde(default, skip_serializing_if = "Vec::is_empty")]
560 denied_calls: Vec<String>,
561 #[serde(default, skip_serializing_if = "Option::is_none")]
562 error: Option<String>,
563 },
564 WorkflowSpawnResult {
567 effect_id: String,
568 #[serde(default, skip_serializing_if = "Vec::is_empty")]
569 started_agent_ids: Vec<String>,
570 #[serde(default, skip_serializing_if = "Vec::is_empty")]
571 failures: Vec<WorkflowSpawnFailure>,
572 #[serde(default, skip_serializing_if = "Option::is_none")]
573 error: Option<String>,
574 },
575 PreemptResult {
576 effect_id: String,
577 #[serde(default, skip_serializing_if = "Option::is_none")]
578 error: Option<String>,
579 },
580 MemoryPersistResult {
581 effect_id: String,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
583 error: Option<String>,
584 },
585 MemoryQueryResult {
586 effect_id: String,
587 #[serde(default, skip_serializing_if = "Vec::is_empty")]
588 hits: Vec<crate::mm::memory::MemoryRecall>,
589 #[serde(default, skip_serializing_if = "Option::is_none")]
590 error: Option<String>,
591 },
592 LargeResultSpoolResult {
593 effect_id: String,
594 #[serde(default, skip_serializing_if = "Option::is_none")]
595 spool_ref: Option<String>,
596 #[serde(default, skip_serializing_if = "Option::is_none")]
597 error: Option<String>,
598 },
599 PageOutArchiveResult {
600 effect_id: String,
601 #[serde(default, skip_serializing_if = "Option::is_none")]
602 archive_ref: Option<String>,
603 #[serde(default, skip_serializing_if = "Option::is_none")]
604 error: Option<String>,
605 },
606 SetKnowledgeBudget {
609 ratio: f64,
610 },
611 SetCriteriaGate {
614 enabled: bool,
615 },
616 SetRepeatFuse {
619 #[serde(default, skip_serializing_if = "Option::is_none")]
620 enabled: Option<bool>,
621 #[serde(default, skip_serializing_if = "Option::is_none")]
622 deny_after: Option<u32>,
623 #[serde(default, skip_serializing_if = "Option::is_none")]
624 terminate_after: Option<u32>,
625 },
626 SetEntropyWatch {
631 #[serde(default, skip_serializing_if = "Option::is_none")]
632 enabled: Option<bool>,
633 #[serde(default, skip_serializing_if = "Option::is_none")]
634 threshold: Option<f64>,
635 #[serde(default, skip_serializing_if = "Option::is_none")]
636 hysteresis: Option<f64>,
637 #[serde(default, skip_serializing_if = "Option::is_none")]
638 cooldown_turns: Option<u32>,
639 #[serde(default, skip_serializing_if = "Option::is_none")]
640 notify_model: Option<bool>,
641 },
642 SetSchedulerBudget {
646 #[serde(default, skip_serializing_if = "Option::is_none")]
647 max_wall_ms: Option<u64>,
648 },
649 SetResourceQuota {
655 quota: crate::governance::quota::ResourceQuota,
656 },
657 ProviderResult {
658 effect_id: String,
659 message: Message,
660 #[serde(default, skip_serializing_if = "Option::is_none")]
661 observed_input_tokens: Option<u32>,
662 #[serde(default, skip_serializing_if = "Option::is_none")]
663 observed_output_tokens: Option<u32>,
664 #[serde(default, skip_serializing_if = "Option::is_none")]
668 now_ms: Option<u64>,
669 #[serde(default, skip_serializing_if = "Option::is_none")]
673 stop_reason: Option<String>,
674 },
675 ToolResults {
676 effect_id: String,
677 results: Vec<ToolResult>,
678 },
679 ProviderError {
686 effect_id: String,
687 message: String,
688 },
689 DeliverSignal {
690 delivery_id: String,
691 attempt: u32,
692 signal: RuntimeSignal,
693 },
694 MilestoneResult {
695 effect_id: String,
696 result: MilestoneCheckResult,
697 },
698 SpawnSubAgent {
700 spec: AgentRunSpec,
701 parent_session_id: String,
702 },
703 LoadWorkflow {
708 spec: crate::orchestration::workflow::WorkflowSpec,
709 parent_session_id: String,
710 #[serde(default, skip_serializing_if = "Vec::is_empty")]
714 resumed_submissions: Vec<Vec<crate::orchestration::workflow::WorkflowNode>>,
715 #[serde(default)]
718 resumed_submission_bases: Vec<u32>,
719 #[serde(default, skip_serializing_if = "Vec::is_empty")]
722 resumed_outcomes: Vec<crate::orchestration::workflow::ResumedNodeOutcome>,
723 },
724 SubAgentCompleted {
726 result: SubAgentResult,
727 },
728 SubmitWorkflowNodes {
733 #[serde(default, skip_serializing_if = "Vec::is_empty")]
734 nodes: Vec<crate::orchestration::workflow::WorkflowNode>,
735 #[serde(default, skip_serializing_if = "Option::is_none")]
739 submitter_agent_id: Option<String>,
740 },
741 SubmitWorkflow {
747 spec: crate::orchestration::workflow::WorkflowSpec,
748 #[serde(default)]
750 parent_session_id: String,
751 #[serde(default, skip_serializing_if = "Option::is_none")]
754 submitter_agent_id: Option<String>,
755 },
756 PageIn {
759 #[serde(default, skip_serializing_if = "Vec::is_empty")]
760 entries: Vec<crate::mm::PageInEntry>,
761 },
762 SetMemoryPolicy {
765 #[serde(default)]
766 memory_path: String,
767 #[serde(default = "default_stale_days")]
768 stale_warning_days: u32,
769 #[serde(default = "default_top_k")]
770 retrieval_top_k: usize,
771 #[serde(default = "default_validation_enabled")]
772 validation_enabled: bool,
773 #[serde(default, skip_serializing_if = "Option::is_none")]
775 max_content_bytes: Option<u32>,
776 #[serde(default, skip_serializing_if = "Option::is_none")]
778 max_name_length: Option<usize>,
779 #[serde(default, skip_serializing_if = "Option::is_none")]
781 promotion_recall_threshold: Option<u64>,
782 },
783 WriteMemory {
785 memory: crate::mm::memory::MemoryRecord,
786 },
787 QueryMemory {
789 query: crate::mm::memory::MemoryQuery,
790 },
791 CompleteRun,
795 CancelOperation {
798 operation_id: String,
799 reason: CancellationReason,
800 #[serde(default, skip_serializing_if = "Vec::is_empty")]
801 pending_call_ids: Vec<String>,
802 },
803}
804
805fn default_stale_days() -> u32 {
806 2
807}
808fn default_top_k() -> usize {
809 5
810}
811fn default_validation_enabled() -> bool {
812 true
813}
814
815#[derive(Debug, Clone, Serialize, Deserialize)]
816pub struct KernelStep {
817 pub version: u32,
818 pub operation_id: String,
819 pub input_event_id: String,
820 pub step_seq: u64,
821 pub actions: Vec<KernelAction>,
822 pub observations: Vec<KernelObservation>,
823 #[serde(default, skip_serializing_if = "Vec::is_empty")]
824 pub faults: Vec<KernelFault>,
825}
826
827impl KernelStep {
828 pub(super) fn empty(
829 operation_id: String,
830 input_event_id: String,
831 step_seq: u64,
832 observations: Vec<KernelObservation>,
833 ) -> Self {
834 Self {
835 version: KERNEL_ABI_VERSION,
836 operation_id,
837 input_event_id,
838 step_seq,
839 actions: Vec::new(),
840 observations,
841 faults: Vec::new(),
842 }
843 }
844
845 pub(super) fn single(
846 operation_id: String,
847 input_event_id: String,
848 step_seq: u64,
849 action: LoopAction,
850 observations: Vec<KernelObservation>,
851 ) -> Self {
852 let effect_id = format!("{operation_id}:step:{step_seq}:effect:0");
853 Self {
854 version: KERNEL_ABI_VERSION,
855 operation_id,
856 input_event_id: input_event_id.clone(),
857 step_seq,
858 actions: vec![KernelAction::from_loop(effect_id, input_event_id, action)],
859 observations,
860 faults: Vec::new(),
861 }
862 }
863
864 pub(super) fn fault(
865 operation_id: String,
866 input_event_id: String,
867 step_seq: u64,
868 fault: KernelFault,
869 ) -> Self {
870 Self {
871 version: KERNEL_ABI_VERSION,
872 operation_id,
873 input_event_id,
874 step_seq,
875 actions: Vec::new(),
876 observations: Vec::new(),
877 faults: vec![fault],
878 }
879 }
880}
881
882#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
883#[serde(rename_all = "snake_case")]
884pub enum KernelFaultCode {
885 VersionMismatch,
886 OperationMismatch,
887 InvalidLifecycle,
888 InvalidConfig,
889 ResourceLimitExceeded,
890 DuplicateEventConflict,
891 UnexpectedEffectResult,
892 TransactionConflict,
893 SnapshotIncompatible,
894}
895
896#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
897pub struct KernelFault {
898 pub code: KernelFaultCode,
899 pub message: String,
900 #[serde(default, skip_serializing_if = "Option::is_none")]
901 pub operation_id: Option<String>,
902 #[serde(default, skip_serializing_if = "Option::is_none")]
903 pub event_id: Option<String>,
904 #[serde(default, skip_serializing_if = "Option::is_none")]
905 pub effect_id: Option<String>,
906}
907
908#[derive(Debug, Clone, Serialize, Deserialize)]
909pub struct KernelAction {
910 pub effect_id: String,
911 pub causation_id: String,
912 #[serde(flatten)]
913 pub effect: KernelEffect,
914}
915
916impl KernelAction {
917 fn from_loop(effect_id: String, causation_id: String, action: LoopAction) -> Self {
918 Self {
919 effect_id,
920 causation_id,
921 effect: action.into(),
922 }
923 }
924}
925
926#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
927pub struct WorkflowSpawnFailure {
928 pub agent_id: String,
929 pub error: String,
930}
931
932#[derive(Debug, Clone, Serialize, Deserialize)]
933#[serde(tag = "kind", rename_all = "snake_case")]
934pub enum KernelEffect {
935 CallProvider {
936 context: RenderedContext,
937 tools: Vec<ToolSchema>,
938 },
939 ExecuteTool {
940 calls: Vec<ToolCall>,
941 },
942 RequestApproval {
943 requests: Vec<crate::scheduler::state_machine::ApprovalRequest>,
944 },
945 SpawnWorkflow {
946 nodes: Vec<crate::orchestration::workflow::WorkflowSpawnInfo>,
947 #[serde(default, skip_serializing_if = "Option::is_none")]
948 budget: Option<crate::orchestration::workflow::WorkflowBudget>,
949 },
950 PreemptSubAgents {
951 agent_ids: Vec<String>,
952 reason: String,
953 },
954 PersistMemory {
955 memory: crate::mm::memory::MemoryRecord,
956 },
957 QueryMemory {
958 query: crate::mm::memory::MemoryQuery,
959 requested_k: usize,
960 },
961 SpoolLargeResult {
962 call_id: String,
963 tool: String,
964 output: String,
965 original_size: u32,
966 preview_size: u32,
967 },
968 ArchivePageOut {
969 turn: u32,
970 action: KernelPressureAction,
971 summary: Option<String>,
972 archived: Vec<Message>,
973 tier: String,
974 },
975 EvaluateMilestone {
976 phase_id: String,
977 criteria: Vec<String>,
978 #[serde(default, skip_serializing_if = "Option::is_none")]
979 verifier: Option<crate::types::milestone::MilestoneVerifier>,
980 #[serde(default, skip_serializing_if = "Vec::is_empty")]
981 required_evidence: Vec<String>,
982 },
983 Done {
984 result: LoopResult,
985 },
986}
987
988impl From<LoopAction> for KernelEffect {
989 fn from(action: LoopAction) -> Self {
990 match action {
991 LoopAction::AwaitingResume => {
992 panic!("AwaitingResume must not be converted to KernelEffect")
993 }
994 LoopAction::CallLLM { context, tools } => Self::CallProvider { context, tools },
995 LoopAction::ExecuteTools { calls } => Self::ExecuteTool { calls },
996 LoopAction::RequestApproval { requests } => Self::RequestApproval { requests },
997 LoopAction::SpawnWorkflow { nodes, budget } => Self::SpawnWorkflow { nodes, budget },
998 LoopAction::PreemptSubAgents { agent_ids, reason } => {
999 Self::PreemptSubAgents { agent_ids, reason }
1000 }
1001 LoopAction::PersistMemory { memory } => Self::PersistMemory { memory },
1002 LoopAction::QueryMemory { query, requested_k } => {
1003 Self::QueryMemory { query, requested_k }
1004 }
1005 LoopAction::SpoolLargeResult {
1006 call_id,
1007 tool,
1008 output,
1009 original_size,
1010 preview_size,
1011 } => Self::SpoolLargeResult {
1012 call_id,
1013 tool,
1014 output,
1015 original_size,
1016 preview_size,
1017 },
1018 LoopAction::ArchivePageOut {
1019 turn,
1020 action,
1021 summary,
1022 archived,
1023 tier,
1024 } => Self::ArchivePageOut {
1025 turn,
1026 action,
1027 summary,
1028 archived,
1029 tier,
1030 },
1031 LoopAction::EvaluateMilestone {
1032 phase_id,
1033 criteria,
1034 verifier,
1035 required_evidence,
1036 } => Self::EvaluateMilestone {
1037 phase_id,
1038 criteria,
1039 verifier,
1040 required_evidence,
1041 },
1042 LoopAction::Done { result } => Self::Done { result },
1043 }
1044 }
1045}
1046
1047#[derive(Debug, Clone, Serialize, Deserialize)]
1048#[serde(tag = "kind", rename_all = "snake_case")]
1049pub enum KernelObservation {
1050 Compressed {
1053 #[serde(default)]
1054 turn: u32,
1055 action: KernelPressureAction,
1056 rho_after: f64,
1057 summary: Option<String>,
1058 archived_count: u32,
1059 #[serde(default, skip_serializing_if = "Option::is_none")]
1063 invalidates_prefix_at: Option<usize>,
1064 },
1065 Renewed {
1066 sprint: u32,
1067 },
1068 ContextBudgetExceeded {
1071 turn: u32,
1072 overflow_kind: crate::context::renderer::ContextBudgetOverflowKind,
1073 required_tokens: u32,
1074 max_tokens: u32,
1075 },
1076 KnowledgeSwept {
1080 turn: u32,
1081 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1082 removed_keys: Vec<String>,
1083 tokens_freed: u32,
1084 },
1085 KnowledgeBudgetExceeded {
1089 turn: u32,
1090 used: u32,
1091 budget: u32,
1092 },
1093 Rollbacked {
1094 turn: u32,
1095 checkpoint_history_len: u32,
1096 #[serde(default, skip_serializing_if = "Option::is_none")]
1097 reason: Option<RollbackReason>,
1098 },
1099 ControlRequestRejected {
1103 turn: u32,
1104 operation: String,
1105 #[serde(default, skip_serializing_if = "Option::is_none")]
1106 subject: Option<String>,
1107 reason: String,
1108 },
1109 CapabilityChanged {
1110 turn: u32,
1111 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1112 added: Vec<String>,
1113 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1114 removed: Vec<String>,
1115 #[serde(default, skip_serializing_if = "Option::is_none")]
1116 change_kind: Option<String>,
1117 #[serde(default, skip_serializing_if = "Option::is_none")]
1118 capability_id: Option<String>,
1119 #[serde(default, skip_serializing_if = "Option::is_none")]
1120 version: Option<String>,
1121 #[serde(default, skip_serializing_if = "Option::is_none")]
1122 mounted_by: Option<String>,
1123 #[serde(default, skip_serializing_if = "Option::is_none")]
1124 mount_reason: Option<String>,
1125 },
1126 MilestoneAdvanced {
1127 turn: u32,
1128 phase_id: String,
1129 capabilities_unlocked: Vec<String>,
1130 },
1131 MilestoneBlocked {
1132 turn: u32,
1133 phase_id: String,
1134 reason: String,
1135 },
1136 CheckpointTaken {
1138 turn: u32,
1139 history_len: u32,
1140 },
1141 RepeatFuseTripped {
1145 turn: u32,
1146 signature: String,
1147 count: u32,
1148 action: String,
1149 },
1150 CriteriaGateFired {
1153 turn: u32,
1154 criteria: Vec<String>,
1155 },
1156 EntropySample {
1161 turn: u32,
1162 score: f64,
1163 score_version: u32,
1164 rho: f64,
1165 repeat_pressure: f64,
1166 failure_rate: f64,
1167 rollbacks_in_window: u32,
1168 window_turns: u32,
1169 },
1170 EntropyAlert {
1174 turn: u32,
1175 score: f64,
1176 threshold: f64,
1177 },
1178 AgentProcessChanged {
1180 turn: u32,
1181 agent_id: String,
1182 parent_session_id: String,
1183 role: String,
1184 isolation: String,
1185 context_inheritance: String,
1186 state: String,
1187 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1188 permitted_capability_ids: Vec<String>,
1189 #[serde(default, skip_serializing_if = "Option::is_none")]
1190 result_termination: Option<String>,
1191 },
1192 WorkflowBatchSpawned {
1195 turn: u32,
1196 nodes: Vec<crate::orchestration::workflow::WorkflowSpawnInfo>,
1197 #[serde(default, skip_serializing_if = "Option::is_none")]
1201 budget: Option<crate::orchestration::workflow::WorkflowBudget>,
1202 },
1203 WorkflowSpawnFailed {
1206 turn: u32,
1207 error: String,
1208 },
1209 WorkflowCompleted {
1211 turn: u32,
1212 node_outcomes: Vec<crate::orchestration::workflow::run::WorkflowNodeOutcome>,
1213 },
1214 AgentPreempted {
1220 turn: u32,
1221 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1222 agent_ids: Vec<String>,
1223 reason: String,
1224 },
1225 AgentPreemptFailed {
1226 turn: u32,
1227 agent_ids: Vec<String>,
1228 reason: String,
1229 error: String,
1230 },
1231 RoundPaced {
1233 turn: u32,
1234 round: u32,
1235 decision: crate::types::result::PaceDecision,
1236 },
1237 WorkflowNodesSubmitted {
1242 turn: u32,
1243 base: u32,
1244 count: u32,
1245 #[serde(default, skip_serializing_if = "Option::is_none")]
1248 submitter: Option<String>,
1249 },
1250 NodesRejected {
1252 turn: u32,
1253 node_index: u32,
1254 reason: String,
1255 },
1256 ToolGated {
1259 turn: u32,
1260 call_id: String,
1261 tool: String,
1262 reason: String,
1263 },
1264 SignalDeliveryDisposed {
1266 turn: u32,
1267 operation_id: String,
1268 delivery_id: String,
1269 attempt: u32,
1270 signal_id: String,
1271 disposition: String,
1272 queue_depth: u32,
1273 },
1274 SignalDisplaced {
1275 turn: u32,
1276 admitted_signal_id: String,
1277 displaced_signal_id: String,
1278 queue_depth: u32,
1279 },
1280 SignalExpired {
1281 turn: u32,
1282 signal_id: String,
1283 queue_depth: u32,
1284 },
1285 SignalsPending {
1286 turn: u32,
1287 depth: u32,
1288 },
1289 BudgetExceeded {
1291 turn: u32,
1292 budget: String,
1293 operation_id: String,
1294 #[serde(default, skip_serializing_if = "Option::is_none")]
1295 reservation_id: Option<String>,
1296 },
1297 BudgetUsageReported {
1299 operation_id: String,
1300 reservation_id: String,
1301 tokens: u64,
1302 subagents: u32,
1303 rounds: u32,
1304 },
1305 OperationCancelled {
1307 turn: u32,
1308 operation_id: String,
1309 reason: CancellationReason,
1310 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1311 pending_call_ids: Vec<String>,
1312 },
1313 Suspended {
1315 turn: u32,
1316 reason: String,
1317 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1318 pending_calls: Vec<String>,
1319 },
1320 Resumed {
1322 turn: u32,
1323 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1324 approved: Vec<String>,
1325 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1326 denied: Vec<String>,
1327 },
1328 ApprovalResolutionFailed {
1329 turn: u32,
1330 error: String,
1331 },
1332 MemoryWritten {
1334 turn: u32,
1335 record_id: String,
1336 scope: crate::mm::memory::MemoryScope,
1337 memory_kind: crate::mm::memory::MemoryKind,
1338 name: String,
1339 size_bytes: u32,
1340 },
1341 MemoryValidationFailed {
1343 turn: u32,
1344 record_id: String,
1345 error: String,
1346 },
1347 MemoryWriteFailed {
1348 turn: u32,
1349 record_id: String,
1350 error: String,
1351 },
1352 MemoryQueried {
1354 turn: u32,
1355 scope: crate::mm::memory::MemoryScope,
1356 query: String,
1357 requested_k: usize,
1358 requires_async_response: bool,
1359 },
1360 MemoryQueryFailed {
1361 turn: u32,
1362 scope: crate::mm::memory::MemoryScope,
1363 query: String,
1364 error: String,
1365 },
1366 MemoryRecalled {
1370 turn: u32,
1371 scope: crate::mm::memory::MemoryScope,
1372 recalls: Vec<crate::mm::memory::MemoryRecallLifecycle>,
1373 },
1374 PromotionSuggested {
1377 turn: u32,
1378 record_id: String,
1379 recall_count: u64,
1380 },
1381 LargeResultSpooled {
1383 turn: u32,
1384 call_id: String,
1385 tool: String,
1386 original_size: u32,
1387 preview_size: u32,
1388 spool_ref: Option<String>,
1389 },
1390 LargeResultSpoolFailed {
1391 turn: u32,
1392 call_id: String,
1393 tool: String,
1394 error: String,
1395 },
1396 PageOutArchived {
1397 turn: u32,
1398 action: KernelPressureAction,
1399 summary: Option<String>,
1400 tier: String,
1401 message_count: u32,
1402 #[serde(default, skip_serializing_if = "Option::is_none")]
1403 archive_ref: Option<String>,
1404 },
1405 PageOutArchiveFailed {
1406 turn: u32,
1407 action: KernelPressureAction,
1408 tier: String,
1409 message_count: u32,
1410 error: String,
1411 },
1412}
1413
1414#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1415#[serde(rename_all = "snake_case")]
1416pub enum KernelPressureAction {
1417 None,
1418 SnipCompact,
1419 MicroCompact,
1420 ContextCollapse,
1421 AutoCompact,
1422}
1423
1424impl From<PressureAction> for KernelPressureAction {
1425 fn from(action: PressureAction) -> Self {
1426 match action {
1427 PressureAction::None => Self::None,
1428 PressureAction::SnipCompact => Self::SnipCompact,
1429 PressureAction::MicroCompact => Self::MicroCompact,
1430 PressureAction::ContextCollapse => Self::ContextCollapse,
1431 PressureAction::AutoCompact => Self::AutoCompact,
1432 }
1433 }
1434}