1use crate::event::EvidenceRef;
7use crate::{
8 identity::EntityIdType, AbstractionLevel, ActionId, AgentId, BeliefId, EnumParseError, GoalId,
9 LearningId, ObservationId, PlanId, StepId, Timestamp, TrajectoryId,
10};
11use serde::{Deserialize, Serialize};
12use std::fmt;
13use std::str::FromStr;
14use uuid::Uuid;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
24pub enum MessageType {
25 TaskDelegation,
27 TaskResult,
29 ContextRequest,
31 ContextShare,
33 CoordinationSignal,
35 Handoff,
37 Interrupt,
39 Heartbeat,
41}
42
43impl MessageType {
44 pub fn as_db_str(&self) -> &'static str {
46 match self {
47 MessageType::TaskDelegation => "task_delegation",
48 MessageType::TaskResult => "task_result",
49 MessageType::ContextRequest => "context_request",
50 MessageType::ContextShare => "context_share",
51 MessageType::CoordinationSignal => "coordination_signal",
52 MessageType::Handoff => "handoff",
53 MessageType::Interrupt => "interrupt",
54 MessageType::Heartbeat => "heartbeat",
55 }
56 }
57
58 pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
60 match s.to_lowercase().replace('_', "").as_str() {
61 "taskdelegation" => Ok(MessageType::TaskDelegation),
62 "taskresult" => Ok(MessageType::TaskResult),
63 "contextrequest" => Ok(MessageType::ContextRequest),
64 "contextshare" => Ok(MessageType::ContextShare),
65 "coordinationsignal" => Ok(MessageType::CoordinationSignal),
66 "handoff" => Ok(MessageType::Handoff),
67 "interrupt" => Ok(MessageType::Interrupt),
68 "heartbeat" => Ok(MessageType::Heartbeat),
69 _ => Err(EnumParseError {
70 enum_name: "MessageType",
71 input: s.to_string(),
72 }),
73 }
74 }
75}
76
77impl fmt::Display for MessageType {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}", self.as_db_str())
80 }
81}
82
83impl FromStr for MessageType {
84 type Err = EnumParseError;
85
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
87 Self::from_db_str(s)
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
93#[serde(rename_all = "snake_case")]
94#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
95pub enum MessagePriority {
96 Low,
98 #[default]
100 Normal,
101 High,
103 Critical,
105}
106
107impl MessagePriority {
108 pub fn as_db_str(&self) -> &'static str {
110 match self {
111 MessagePriority::Low => "low",
112 MessagePriority::Normal => "normal",
113 MessagePriority::High => "high",
114 MessagePriority::Critical => "critical",
115 }
116 }
117
118 pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
120 match s.to_lowercase().as_str() {
121 "low" => Ok(MessagePriority::Low),
122 "normal" => Ok(MessagePriority::Normal),
123 "high" => Ok(MessagePriority::High),
124 "critical" => Ok(MessagePriority::Critical),
125 _ => Err(EnumParseError {
126 enum_name: "MessagePriority",
127 input: s.to_string(),
128 }),
129 }
130 }
131}
132
133impl fmt::Display for MessagePriority {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(f, "{}", self.as_db_str())
136 }
137}
138
139impl FromStr for MessagePriority {
140 type Err = EnumParseError;
141
142 fn from_str(s: &str) -> Result<Self, Self::Err> {
143 Self::from_db_str(s)
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
149#[serde(rename_all = "snake_case")]
150#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
151pub enum AgentTarget {
152 ById(AgentId),
153 ByType(crate::AgentType),
154}
155
156impl AgentTarget {
157 pub fn as_agent_id(&self) -> Option<AgentId> {
159 match self {
160 AgentTarget::ById(id) => Some(*id),
161 AgentTarget::ByType(_) => None,
162 }
163 }
164
165 pub fn as_agent_type(&self) -> Option<&crate::AgentType> {
167 match self {
168 AgentTarget::ById(_) => None,
169 AgentTarget::ByType(t) => Some(t),
170 }
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
180#[serde(rename_all = "snake_case")]
181#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
182pub enum PermissionScope {
183 Own,
185 Team,
187 Global,
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
193#[serde(rename_all = "snake_case")]
194#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
195pub enum MemoryRegion {
196 Private,
198 Team,
200 Public,
202 Collaborative,
204}
205
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
208#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
209pub struct MemoryPermission {
210 pub memory_type: crate::MemoryType,
212 pub scope: PermissionScope,
214 pub filter: Option<String>,
216}
217
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
220#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
221pub struct MemoryAccess {
222 pub read: Vec<MemoryPermission>,
224 pub write: Vec<MemoryPermission>,
226}
227
228impl Default for MemoryAccess {
229 fn default() -> Self {
230 Self {
231 read: vec![MemoryPermission {
232 memory_type: crate::MemoryType::All,
233 scope: PermissionScope::Own,
234 filter: None,
235 }],
236 write: vec![MemoryPermission {
237 memory_type: crate::MemoryType::All,
238 scope: PermissionScope::Own,
239 filter: None,
240 }],
241 }
242 }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
251#[serde(rename_all = "snake_case")]
252#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
253pub enum HandoffReason {
254 CapabilityMismatch,
256 LoadBalancing,
258 Specialization,
260 Escalation,
262 Timeout,
264 Failure,
266 Scheduled,
268}
269
270impl HandoffReason {
271 pub fn as_db_str(&self) -> &'static str {
273 match self {
274 HandoffReason::CapabilityMismatch => "capability_mismatch",
275 HandoffReason::LoadBalancing => "load_balancing",
276 HandoffReason::Specialization => "specialization",
277 HandoffReason::Escalation => "escalation",
278 HandoffReason::Timeout => "timeout",
279 HandoffReason::Failure => "failure",
280 HandoffReason::Scheduled => "scheduled",
281 }
282 }
283
284 pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
286 match s.to_lowercase().replace('_', "").as_str() {
287 "capabilitymismatch" => Ok(HandoffReason::CapabilityMismatch),
288 "loadbalancing" => Ok(HandoffReason::LoadBalancing),
289 "specialization" => Ok(HandoffReason::Specialization),
290 "escalation" => Ok(HandoffReason::Escalation),
291 "timeout" => Ok(HandoffReason::Timeout),
292 "failure" | "failed" => Ok(HandoffReason::Failure),
293 "scheduled" => Ok(HandoffReason::Scheduled),
294 _ => Err(EnumParseError {
295 enum_name: "HandoffReason",
296 input: s.to_string(),
297 }),
298 }
299 }
300}
301
302impl fmt::Display for HandoffReason {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 write!(f, "{}", self.as_db_str())
305 }
306}
307
308impl FromStr for HandoffReason {
309 type Err = EnumParseError;
310
311 fn from_str(s: &str) -> Result<Self, Self::Err> {
312 Self::from_db_str(s)
313 }
314}
315
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
322#[serde(rename_all = "snake_case")]
323#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
324pub enum ConflictType {
325 ConcurrentWrite,
327 ContradictingFact,
329 IncompatibleDecision,
331 ResourceContention,
333 GoalConflict,
335}
336
337impl ConflictType {
338 pub fn as_db_str(&self) -> &'static str {
340 match self {
341 ConflictType::ConcurrentWrite => "concurrent_write",
342 ConflictType::ContradictingFact => "contradicting_fact",
343 ConflictType::IncompatibleDecision => "incompatible_decision",
344 ConflictType::ResourceContention => "resource_contention",
345 ConflictType::GoalConflict => "goal_conflict",
346 }
347 }
348
349 pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
351 match s.to_lowercase().replace('_', "").as_str() {
352 "concurrentwrite" => Ok(ConflictType::ConcurrentWrite),
353 "contradictingfact" => Ok(ConflictType::ContradictingFact),
354 "incompatibledecision" => Ok(ConflictType::IncompatibleDecision),
355 "resourcecontention" => Ok(ConflictType::ResourceContention),
356 "goalconflict" => Ok(ConflictType::GoalConflict),
357 _ => Err(EnumParseError {
358 enum_name: "ConflictType",
359 input: s.to_string(),
360 }),
361 }
362 }
363}
364
365impl fmt::Display for ConflictType {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 write!(f, "{}", self.as_db_str())
368 }
369}
370
371impl FromStr for ConflictType {
372 type Err = EnumParseError;
373
374 fn from_str(s: &str) -> Result<Self, Self::Err> {
375 Self::from_db_str(s)
376 }
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
381#[serde(rename_all = "snake_case")]
382#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
383pub enum ConflictStatus {
384 Detected,
386 Resolving,
388 Resolved,
390 Escalated,
392}
393
394impl ConflictStatus {
395 pub fn as_db_str(&self) -> &'static str {
397 match self {
398 ConflictStatus::Detected => "detected",
399 ConflictStatus::Resolving => "resolving",
400 ConflictStatus::Resolved => "resolved",
401 ConflictStatus::Escalated => "escalated",
402 }
403 }
404
405 pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
407 match s.to_lowercase().as_str() {
408 "detected" => Ok(ConflictStatus::Detected),
409 "resolving" => Ok(ConflictStatus::Resolving),
410 "resolved" => Ok(ConflictStatus::Resolved),
411 "escalated" => Ok(ConflictStatus::Escalated),
412 _ => Err(EnumParseError {
413 enum_name: "ConflictStatus",
414 input: s.to_string(),
415 }),
416 }
417 }
418
419 pub fn is_terminal(&self) -> bool {
421 matches!(self, ConflictStatus::Resolved | ConflictStatus::Escalated)
422 }
423}
424
425impl fmt::Display for ConflictStatus {
426 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427 write!(f, "{}", self.as_db_str())
428 }
429}
430
431impl FromStr for ConflictStatus {
432 type Err = EnumParseError;
433
434 fn from_str(s: &str) -> Result<Self, Self::Err> {
435 Self::from_db_str(s)
436 }
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
441#[serde(rename_all = "snake_case")]
442#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
443pub enum ResolutionStrategy {
444 LastWriteWins,
446 FirstWriteWins,
448 HighestConfidence,
450 Merge,
452 Escalate,
454 RejectBoth,
456}
457
458impl ResolutionStrategy {
459 pub fn as_db_str(&self) -> &'static str {
461 match self {
462 ResolutionStrategy::LastWriteWins => "last_write_wins",
463 ResolutionStrategy::FirstWriteWins => "first_write_wins",
464 ResolutionStrategy::HighestConfidence => "highest_confidence",
465 ResolutionStrategy::Merge => "merge",
466 ResolutionStrategy::Escalate => "escalate",
467 ResolutionStrategy::RejectBoth => "reject_both",
468 }
469 }
470
471 pub fn from_db_str(s: &str) -> Result<Self, EnumParseError> {
473 match s.to_lowercase().replace('_', "").as_str() {
474 "lastwritewins" => Ok(ResolutionStrategy::LastWriteWins),
475 "firstwritewins" => Ok(ResolutionStrategy::FirstWriteWins),
476 "highestconfidence" => Ok(ResolutionStrategy::HighestConfidence),
477 "merge" => Ok(ResolutionStrategy::Merge),
478 "escalate" => Ok(ResolutionStrategy::Escalate),
479 "rejectboth" => Ok(ResolutionStrategy::RejectBoth),
480 _ => Err(EnumParseError {
481 enum_name: "ResolutionStrategy",
482 input: s.to_string(),
483 }),
484 }
485 }
486}
487
488impl fmt::Display for ResolutionStrategy {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 write!(f, "{}", self.as_db_str())
491 }
492}
493
494impl FromStr for ResolutionStrategy {
495 type Err = EnumParseError;
496
497 fn from_str(s: &str) -> Result<Self, Self::Err> {
498 Self::from_db_str(s)
499 }
500}
501
502#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
508#[serde(rename_all = "snake_case")]
509#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
510pub enum ConflictResolution {
511 #[default]
513 LastWriteWins,
514 HighestConfidence,
516 Escalate,
518}
519
520#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
526#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
527pub struct MemoryRegionConfig {
528 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
530 pub region_id: Uuid,
531 pub region_type: MemoryRegion,
533 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
535 pub owner_agent_id: Uuid,
536 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
538 pub team_id: Option<Uuid>,
539
540 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
542 pub readers: Vec<Uuid>,
543 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
545 pub writers: Vec<Uuid>,
546
547 pub require_lock: bool,
549 pub conflict_resolution: ConflictResolution,
551 pub version_tracking: bool,
553}
554
555impl MemoryRegionConfig {
556 pub fn private(owner_agent_id: Uuid) -> Self {
558 Self {
559 region_id: Uuid::now_v7(),
560 region_type: MemoryRegion::Private,
561 owner_agent_id,
562 team_id: None,
563 readers: vec![owner_agent_id],
564 writers: vec![owner_agent_id],
565 require_lock: false,
566 conflict_resolution: ConflictResolution::LastWriteWins,
567 version_tracking: false,
568 }
569 }
570
571 pub fn team(owner_agent_id: Uuid, team_id: Uuid) -> Self {
573 Self {
574 region_id: Uuid::now_v7(),
575 region_type: MemoryRegion::Team,
576 owner_agent_id,
577 team_id: Some(team_id),
578 readers: Vec::new(),
579 writers: Vec::new(),
580 require_lock: false,
581 conflict_resolution: ConflictResolution::LastWriteWins,
582 version_tracking: true,
583 }
584 }
585
586 pub fn public(owner_agent_id: Uuid) -> Self {
588 Self {
589 region_id: Uuid::now_v7(),
590 region_type: MemoryRegion::Public,
591 owner_agent_id,
592 team_id: None,
593 readers: Vec::new(),
594 writers: vec![owner_agent_id],
595 require_lock: false,
596 conflict_resolution: ConflictResolution::LastWriteWins,
597 version_tracking: false,
598 }
599 }
600
601 pub fn collaborative(owner_agent_id: Uuid) -> Self {
603 Self {
604 region_id: Uuid::now_v7(),
605 region_type: MemoryRegion::Collaborative,
606 owner_agent_id,
607 team_id: None,
608 readers: Vec::new(),
609 writers: Vec::new(),
610 require_lock: true,
611 conflict_resolution: ConflictResolution::Escalate,
612 version_tracking: true,
613 }
614 }
615
616 pub fn add_reader(&mut self, agent_id: Uuid) {
618 if !self.readers.contains(&agent_id) {
619 self.readers.push(agent_id);
620 }
621 }
622
623 pub fn add_writer(&mut self, agent_id: Uuid) {
625 if !self.writers.contains(&agent_id) {
626 self.writers.push(agent_id);
627 }
628 }
629
630 pub fn can_read(&self, agent_id: Uuid) -> bool {
632 match self.region_type {
633 MemoryRegion::Private => agent_id == self.owner_agent_id,
634 MemoryRegion::Team => {
635 agent_id == self.owner_agent_id || self.readers.contains(&agent_id)
636 }
637 MemoryRegion::Public | MemoryRegion::Collaborative => true,
638 }
639 }
640
641 pub fn can_write(&self, agent_id: Uuid) -> bool {
643 match self.region_type {
644 MemoryRegion::Private => agent_id == self.owner_agent_id,
645 MemoryRegion::Team => {
646 agent_id == self.owner_agent_id || self.writers.contains(&agent_id)
647 }
648 MemoryRegion::Public => agent_id == self.owner_agent_id,
649 MemoryRegion::Collaborative => true,
650 }
651 }
652}
653
654#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
664#[serde(rename_all = "snake_case")]
665#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
666pub enum GoalStatus {
667 #[default]
669 Pending,
670 Active,
672 Achieved,
674 Failed,
676 Abandoned,
678 Suspended,
680}
681
682#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
684#[serde(rename_all = "snake_case")]
685#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
686pub enum GoalType {
687 #[default]
689 Terminal,
690 Subgoal,
692 Milestone,
694 Invariant,
696}
697
698#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
700#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
701pub struct SuccessCriterion {
702 pub description: String,
704 pub measurable: bool,
706 pub target_value: Option<String>,
708 pub current_value: Option<String>,
710 pub satisfied: bool,
712}
713
714impl SuccessCriterion {
715 pub fn new(description: impl Into<String>) -> Self {
717 Self {
718 description: description.into(),
719 measurable: false,
720 target_value: None,
721 current_value: None,
722 satisfied: false,
723 }
724 }
725
726 pub fn measurable(mut self, target: impl Into<String>) -> Self {
728 self.measurable = true;
729 self.target_value = Some(target.into());
730 self
731 }
732
733 pub fn update(&mut self, value: impl Into<String>) {
735 self.current_value = Some(value.into());
736 }
737
738 pub fn satisfy(&mut self) {
740 self.satisfied = true;
741 }
742}
743
744#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
746#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
747pub struct AgentGoal {
748 pub goal_id: GoalId,
750 pub agent_id: AgentId,
752 pub trajectory_id: Option<TrajectoryId>,
754 pub description: String,
756 pub goal_type: GoalType,
758 pub status: GoalStatus,
760 pub success_criteria: Vec<SuccessCriterion>,
762 pub priority: i32,
764 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
766 pub deadline: Option<Timestamp>,
767 pub parent_goal_id: Option<GoalId>,
769 pub child_goal_ids: Vec<GoalId>,
771 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
773 pub created_at: Timestamp,
774 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
776 pub started_at: Option<Timestamp>,
777 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
779 pub completed_at: Option<Timestamp>,
780 pub failure_reason: Option<String>,
782}
783
784impl AgentGoal {
785 pub fn new(agent_id: AgentId, description: impl Into<String>, goal_type: GoalType) -> Self {
787 Self {
788 goal_id: GoalId::now_v7(),
789 agent_id,
790 trajectory_id: None,
791 description: description.into(),
792 goal_type,
793 status: GoalStatus::Pending,
794 success_criteria: Vec::new(),
795 priority: 0,
796 deadline: None,
797 parent_goal_id: None,
798 child_goal_ids: Vec::new(),
799 created_at: chrono::Utc::now(),
800 started_at: None,
801 completed_at: None,
802 failure_reason: None,
803 }
804 }
805
806 pub fn with_trajectory(mut self, trajectory_id: TrajectoryId) -> Self {
808 self.trajectory_id = Some(trajectory_id);
809 self
810 }
811
812 pub fn with_parent(mut self, parent_id: GoalId) -> Self {
814 self.parent_goal_id = Some(parent_id);
815 self
816 }
817
818 pub fn with_criterion(mut self, criterion: SuccessCriterion) -> Self {
820 self.success_criteria.push(criterion);
821 self
822 }
823
824 pub fn with_priority(mut self, priority: i32) -> Self {
826 self.priority = priority;
827 self
828 }
829
830 pub fn with_deadline(mut self, deadline: Timestamp) -> Self {
832 self.deadline = Some(deadline);
833 self
834 }
835
836 pub fn start(&mut self) {
838 self.status = GoalStatus::Active;
839 self.started_at = Some(chrono::Utc::now());
840 }
841
842 pub fn achieve(&mut self) {
844 self.status = GoalStatus::Achieved;
845 self.completed_at = Some(chrono::Utc::now());
846 }
847
848 pub fn fail(&mut self, reason: impl Into<String>) {
850 self.status = GoalStatus::Failed;
851 self.failure_reason = Some(reason.into());
852 self.completed_at = Some(chrono::Utc::now());
853 }
854
855 pub fn all_criteria_satisfied(&self) -> bool {
857 self.success_criteria.iter().all(|c| c.satisfied)
858 }
859}
860
861#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
867#[serde(rename_all = "snake_case")]
868#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
869pub enum PlanStatus {
870 #[default]
872 Draft,
873 Ready,
875 InProgress,
877 Completed,
879 Failed,
881 Abandoned,
883}
884
885#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
887#[serde(rename_all = "snake_case")]
888#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
889pub enum StepStatus {
890 #[default]
892 Pending,
893 Ready,
895 InProgress,
897 Completed,
899 Failed,
901 Skipped,
903 Blocked,
905}
906
907#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
909#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
910pub struct PlanStep {
911 pub step_id: StepId,
913 pub index: i32,
915 pub description: String,
917 pub action_type: ActionType,
919 pub preconditions: Vec<String>,
921 pub postconditions: Vec<String>,
923 pub depends_on: Vec<StepId>,
925 pub estimated_tokens: Option<i32>,
927 pub status: StepStatus,
929}
930
931impl PlanStep {
932 pub fn new(index: i32, description: impl Into<String>, action_type: ActionType) -> Self {
934 Self {
935 step_id: StepId::now_v7(),
936 index,
937 description: description.into(),
938 action_type,
939 preconditions: Vec::new(),
940 postconditions: Vec::new(),
941 depends_on: Vec::new(),
942 estimated_tokens: None,
943 status: StepStatus::Pending,
944 }
945 }
946
947 pub fn with_precondition(mut self, condition: impl Into<String>) -> Self {
949 self.preconditions.push(condition.into());
950 self
951 }
952
953 pub fn with_postcondition(mut self, condition: impl Into<String>) -> Self {
955 self.postconditions.push(condition.into());
956 self
957 }
958
959 pub fn depends_on(mut self, step_id: StepId) -> Self {
961 self.depends_on.push(step_id);
962 self
963 }
964}
965
966#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
968#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
969pub struct PlanCost {
970 pub estimated_tokens: i32,
972 pub estimated_duration_ms: i64,
974 pub monetary_cost_usd: Option<f64>,
976}
977
978impl PlanCost {
979 pub fn new(tokens: i32, duration_ms: i64) -> Self {
981 Self {
982 estimated_tokens: tokens,
983 estimated_duration_ms: duration_ms,
984 monetary_cost_usd: None,
985 }
986 }
987
988 pub fn with_monetary_cost(mut self, cost_usd: f64) -> Self {
990 self.monetary_cost_usd = Some(cost_usd);
991 self
992 }
993}
994
995#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
997#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
998pub struct AgentPlan {
999 pub plan_id: PlanId,
1001 pub agent_id: AgentId,
1003 pub goal_id: GoalId,
1005 pub description: String,
1007 pub status: PlanStatus,
1009 pub steps: Vec<PlanStep>,
1011 pub estimated_cost: Option<PlanCost>,
1013 pub actual_cost: Option<PlanCost>,
1015 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
1017 pub created_at: Timestamp,
1018 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
1020 pub started_at: Option<Timestamp>,
1021 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
1023 pub completed_at: Option<Timestamp>,
1024}
1025
1026impl AgentPlan {
1027 pub fn new(agent_id: AgentId, goal_id: GoalId, description: impl Into<String>) -> Self {
1029 Self {
1030 plan_id: PlanId::now_v7(),
1031 agent_id,
1032 goal_id,
1033 description: description.into(),
1034 status: PlanStatus::Draft,
1035 steps: Vec::new(),
1036 estimated_cost: None,
1037 actual_cost: None,
1038 created_at: chrono::Utc::now(),
1039 started_at: None,
1040 completed_at: None,
1041 }
1042 }
1043
1044 pub fn add_step(&mut self, step: PlanStep) {
1046 self.steps.push(step);
1047 }
1048
1049 pub fn with_estimated_cost(mut self, cost: PlanCost) -> Self {
1051 self.estimated_cost = Some(cost);
1052 self
1053 }
1054
1055 pub fn ready(&mut self) {
1057 self.status = PlanStatus::Ready;
1058 }
1059
1060 pub fn start(&mut self) {
1062 self.status = PlanStatus::InProgress;
1063 self.started_at = Some(chrono::Utc::now());
1064 }
1065
1066 pub fn complete(&mut self, actual_cost: Option<PlanCost>) {
1068 self.status = PlanStatus::Completed;
1069 self.actual_cost = actual_cost;
1070 self.completed_at = Some(chrono::Utc::now());
1071 }
1072
1073 pub fn next_step(&self) -> Option<&PlanStep> {
1075 self.steps
1076 .iter()
1077 .find(|s| s.status == StepStatus::Pending || s.status == StepStatus::Ready)
1078 }
1079}
1080
1081#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1087#[serde(rename_all = "snake_case")]
1088#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1089pub enum ActionType {
1090 #[default]
1092 Operation,
1093 ToolCall,
1095 ModelQuery,
1097 Decision,
1099 Communication,
1101 Observation,
1103 MemoryAccess,
1105}
1106
1107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1109#[serde(rename_all = "snake_case")]
1110#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1111pub enum ActionStatus {
1112 #[default]
1114 Pending,
1115 InProgress,
1117 Completed,
1119 Failed,
1121 Retrying,
1123 Cancelled,
1125}
1126
1127#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
1129#[serde(rename_all = "snake_case")]
1130#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1131pub enum BackoffStrategy {
1132 #[default]
1134 None,
1135 Fixed { delay_ms: i64 },
1137 Linear { base_ms: i64, increment_ms: i64 },
1139 Exponential {
1141 base_ms: i64,
1142 multiplier: f64,
1143 max_ms: i64,
1144 },
1145}
1146
1147impl BackoffStrategy {
1148 pub fn delay_for_attempt(&self, attempt: i32) -> i64 {
1150 match self {
1151 BackoffStrategy::None => 0,
1152 BackoffStrategy::Fixed { delay_ms } => *delay_ms,
1153 BackoffStrategy::Linear {
1154 base_ms,
1155 increment_ms,
1156 } => base_ms + (attempt as i64 * increment_ms),
1157 BackoffStrategy::Exponential {
1158 base_ms,
1159 multiplier,
1160 max_ms,
1161 } => {
1162 let delay = (*base_ms as f64) * multiplier.powi(attempt);
1163 (delay as i64).min(*max_ms)
1164 }
1165 }
1166 }
1167}
1168
1169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1171#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1172pub struct RetryPolicy {
1173 pub max_attempts: i32,
1175 pub backoff: BackoffStrategy,
1177 pub timeout_per_attempt_ms: i64,
1179}
1180
1181impl Default for RetryPolicy {
1182 fn default() -> Self {
1183 Self {
1184 max_attempts: 3,
1185 backoff: BackoffStrategy::Exponential {
1186 base_ms: 100,
1187 multiplier: 2.0,
1188 max_ms: 10_000,
1189 },
1190 timeout_per_attempt_ms: 30_000,
1191 }
1192 }
1193}
1194
1195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1197#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1198pub struct AgentAction {
1199 pub action_id: ActionId,
1201 pub agent_id: AgentId,
1203 pub step_id: Option<StepId>,
1205 pub action_type: ActionType,
1207 pub description: String,
1209 pub parameters: Option<serde_json::Value>,
1211 pub retry_policy: Option<RetryPolicy>,
1213 pub timeout_ms: Option<i64>,
1215 pub status: ActionStatus,
1217 pub attempt_count: i32,
1219 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
1221 pub created_at: Timestamp,
1222 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
1224 pub started_at: Option<Timestamp>,
1225 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
1227 pub completed_at: Option<Timestamp>,
1228}
1229
1230impl AgentAction {
1231 pub fn new(agent_id: AgentId, action_type: ActionType, description: impl Into<String>) -> Self {
1233 Self {
1234 action_id: ActionId::now_v7(),
1235 agent_id,
1236 step_id: None,
1237 action_type,
1238 description: description.into(),
1239 parameters: None,
1240 retry_policy: None,
1241 timeout_ms: None,
1242 status: ActionStatus::Pending,
1243 attempt_count: 0,
1244 created_at: chrono::Utc::now(),
1245 started_at: None,
1246 completed_at: None,
1247 }
1248 }
1249
1250 pub fn with_step(mut self, step_id: StepId) -> Self {
1252 self.step_id = Some(step_id);
1253 self
1254 }
1255
1256 pub fn with_parameters(mut self, params: serde_json::Value) -> Self {
1258 self.parameters = Some(params);
1259 self
1260 }
1261
1262 pub fn with_retry_policy(mut self, policy: RetryPolicy) -> Self {
1264 self.retry_policy = Some(policy);
1265 self
1266 }
1267
1268 pub fn with_timeout(mut self, timeout_ms: i64) -> Self {
1270 self.timeout_ms = Some(timeout_ms);
1271 self
1272 }
1273
1274 pub fn start(&mut self) {
1276 self.status = ActionStatus::InProgress;
1277 self.attempt_count += 1;
1278 if self.started_at.is_none() {
1279 self.started_at = Some(chrono::Utc::now());
1280 }
1281 }
1282
1283 pub fn complete(&mut self) {
1285 self.status = ActionStatus::Completed;
1286 self.completed_at = Some(chrono::Utc::now());
1287 }
1288
1289 pub fn fail(&mut self) {
1291 self.status = ActionStatus::Failed;
1292 self.completed_at = Some(chrono::Utc::now());
1293 }
1294
1295 pub fn can_retry(&self) -> bool {
1297 if let Some(policy) = &self.retry_policy {
1298 self.attempt_count < policy.max_attempts
1299 } else {
1300 false
1301 }
1302 }
1303}
1304
1305#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1311#[serde(rename_all = "snake_case")]
1312#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1313pub enum BeliefSource {
1314 #[default]
1316 Observation,
1317 Inference,
1319 Communication,
1321 MemoryRecall,
1323 Assumption,
1325 UserProvided,
1327}
1328
1329#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1331#[serde(rename_all = "snake_case")]
1332#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1333pub enum BeliefType {
1334 #[default]
1336 Fact,
1337 Hypothesis,
1339 Uncertainty,
1341}
1342
1343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1345#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1346pub struct Belief {
1347 pub belief_id: BeliefId,
1349 pub agent_id: AgentId,
1351 pub belief_type: BeliefType,
1353 pub content: String,
1355 pub confidence: f32,
1357 pub source: BeliefSource,
1359 pub evidence_refs: Vec<EvidenceRef>,
1361 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
1363 pub created_at: Timestamp,
1364 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
1366 pub updated_at: Timestamp,
1367 pub superseded_by: Option<BeliefId>,
1369}
1370
1371impl Belief {
1372 pub fn new(
1374 agent_id: AgentId,
1375 content: impl Into<String>,
1376 belief_type: BeliefType,
1377 source: BeliefSource,
1378 ) -> Self {
1379 let now = chrono::Utc::now();
1380 Self {
1381 belief_id: BeliefId::now_v7(),
1382 agent_id,
1383 belief_type,
1384 content: content.into(),
1385 confidence: 1.0,
1386 source,
1387 evidence_refs: Vec::new(),
1388 created_at: now,
1389 updated_at: now,
1390 superseded_by: None,
1391 }
1392 }
1393
1394 pub fn with_confidence(mut self, confidence: f32) -> Self {
1396 self.confidence = confidence.clamp(0.0, 1.0);
1397 self
1398 }
1399
1400 pub fn with_evidence(mut self, evidence: EvidenceRef) -> Self {
1402 self.evidence_refs.push(evidence);
1403 self
1404 }
1405
1406 pub fn update_confidence(&mut self, confidence: f32) {
1408 self.confidence = confidence.clamp(0.0, 1.0);
1409 self.updated_at = chrono::Utc::now();
1410 }
1411
1412 pub fn supersede(&mut self, new_belief_id: BeliefId) {
1414 self.superseded_by = Some(new_belief_id);
1415 self.updated_at = chrono::Utc::now();
1416 }
1417
1418 pub fn is_active(&self) -> bool {
1420 self.superseded_by.is_none()
1421 }
1422}
1423
1424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1426#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1427pub struct AgentBeliefs {
1428 pub agent_id: AgentId,
1430 pub facts: Vec<Belief>,
1432 pub hypotheses: Vec<Belief>,
1434 pub uncertainties: Vec<Belief>,
1436 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
1438 pub last_updated: Timestamp,
1439}
1440
1441impl AgentBeliefs {
1442 pub fn new(agent_id: AgentId) -> Self {
1444 Self {
1445 agent_id,
1446 facts: Vec::new(),
1447 hypotheses: Vec::new(),
1448 uncertainties: Vec::new(),
1449 last_updated: chrono::Utc::now(),
1450 }
1451 }
1452
1453 pub fn add(&mut self, belief: Belief) {
1455 match belief.belief_type {
1456 BeliefType::Fact => self.facts.push(belief),
1457 BeliefType::Hypothesis => self.hypotheses.push(belief),
1458 BeliefType::Uncertainty => self.uncertainties.push(belief),
1459 }
1460 self.last_updated = chrono::Utc::now();
1461 }
1462
1463 pub fn active_beliefs(&self) -> impl Iterator<Item = &Belief> {
1465 self.facts
1466 .iter()
1467 .chain(self.hypotheses.iter())
1468 .chain(self.uncertainties.iter())
1469 .filter(|b| b.is_active())
1470 }
1471}
1472
1473#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1479#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1480pub struct AgentObservation {
1481 pub observation_id: ObservationId,
1483 pub agent_id: AgentId,
1485 pub action_id: ActionId,
1487 pub success: bool,
1489 pub result: Option<serde_json::Value>,
1491 pub error: Option<String>,
1493 pub duration_ms: i64,
1495 pub tokens_used: Option<i32>,
1497 pub cost_usd: Option<f64>,
1499 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
1501 pub timestamp: Timestamp,
1502 pub belief_updates: Vec<BeliefId>,
1504 pub learnings: Vec<Learning>,
1506}
1507
1508impl AgentObservation {
1509 pub fn new(agent_id: AgentId, action_id: ActionId, success: bool, duration_ms: i64) -> Self {
1511 Self {
1512 observation_id: ObservationId::now_v7(),
1513 agent_id,
1514 action_id,
1515 success,
1516 result: None,
1517 error: None,
1518 duration_ms,
1519 tokens_used: None,
1520 cost_usd: None,
1521 timestamp: chrono::Utc::now(),
1522 belief_updates: Vec::new(),
1523 learnings: Vec::new(),
1524 }
1525 }
1526
1527 pub fn with_result(mut self, result: serde_json::Value) -> Self {
1529 self.result = Some(result);
1530 self
1531 }
1532
1533 pub fn with_error(mut self, error: impl Into<String>) -> Self {
1535 self.error = Some(error.into());
1536 self
1537 }
1538
1539 pub fn with_tokens(mut self, tokens: i32) -> Self {
1541 self.tokens_used = Some(tokens);
1542 self
1543 }
1544
1545 pub fn with_cost(mut self, cost_usd: f64) -> Self {
1547 self.cost_usd = Some(cost_usd);
1548 self
1549 }
1550
1551 pub fn add_belief_update(&mut self, belief_id: BeliefId) {
1553 self.belief_updates.push(belief_id);
1554 }
1555
1556 pub fn add_learning(&mut self, learning: Learning) {
1558 self.learnings.push(learning);
1559 }
1560}
1561
1562#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1564#[serde(rename_all = "snake_case")]
1565#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1566pub enum LearningType {
1567 #[default]
1569 FactualUpdate,
1570 PatternRecognition,
1572 StrategyRefinement,
1574 ErrorCorrection,
1576 CapabilityUpdate,
1578}
1579
1580#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1582#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1583pub struct Learning {
1584 pub learning_id: LearningId,
1586 pub observation_id: ObservationId,
1588 pub learning_type: LearningType,
1590 pub content: String,
1592 pub abstraction_level: AbstractionLevel,
1594 pub applicability: Option<String>,
1596 pub confidence: f32,
1598 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
1600 pub created_at: Timestamp,
1601}
1602
1603impl Learning {
1604 pub fn new(
1606 observation_id: ObservationId,
1607 learning_type: LearningType,
1608 content: impl Into<String>,
1609 ) -> Self {
1610 Self {
1611 learning_id: LearningId::now_v7(),
1612 observation_id,
1613 learning_type,
1614 content: content.into(),
1615 abstraction_level: AbstractionLevel::Raw,
1616 applicability: None,
1617 confidence: 1.0,
1618 created_at: chrono::Utc::now(),
1619 }
1620 }
1621
1622 pub fn with_abstraction(mut self, level: AbstractionLevel) -> Self {
1624 self.abstraction_level = level;
1625 self
1626 }
1627
1628 pub fn with_applicability(mut self, applicability: impl Into<String>) -> Self {
1630 self.applicability = Some(applicability.into());
1631 self
1632 }
1633
1634 pub fn with_confidence(mut self, confidence: f32) -> Self {
1636 self.confidence = confidence.clamp(0.0, 1.0);
1637 self
1638 }
1639}
1640
1641#[cfg(test)]
1642mod tests {
1643 use super::*;
1644 use serde_json::json;
1645
1646 #[test]
1647 fn test_message_type_roundtrip() {
1648 for mt in [
1649 MessageType::TaskDelegation,
1650 MessageType::TaskResult,
1651 MessageType::ContextRequest,
1652 MessageType::ContextShare,
1653 MessageType::CoordinationSignal,
1654 MessageType::Handoff,
1655 MessageType::Interrupt,
1656 MessageType::Heartbeat,
1657 ] {
1658 let s = mt.as_db_str();
1659 let parsed = MessageType::from_db_str(s).expect("MessageType roundtrip should succeed");
1660 assert_eq!(mt, parsed);
1661 }
1662 }
1663
1664 #[test]
1665 fn test_message_priority_roundtrip() {
1666 for mp in [
1667 MessagePriority::Low,
1668 MessagePriority::Normal,
1669 MessagePriority::High,
1670 MessagePriority::Critical,
1671 ] {
1672 let s = mp.as_db_str();
1673 let parsed =
1674 MessagePriority::from_db_str(s).expect("MessagePriority roundtrip should succeed");
1675 assert_eq!(mp, parsed);
1676 }
1677 }
1678
1679 #[test]
1680 fn test_conflict_status_terminal() {
1681 assert!(!ConflictStatus::Detected.is_terminal());
1682 assert!(!ConflictStatus::Resolving.is_terminal());
1683 assert!(ConflictStatus::Resolved.is_terminal());
1684 assert!(ConflictStatus::Escalated.is_terminal());
1685 }
1686
1687 #[test]
1688 fn test_agent_goal_builders_and_lifecycle() {
1689 let agent_id = AgentId::now_v7();
1690 let trajectory_id = TrajectoryId::now_v7();
1691 let parent_id = GoalId::now_v7();
1692 let deadline = chrono::Utc::now();
1693 let criterion = SuccessCriterion::new("done");
1694
1695 let mut goal = AgentGoal::new(agent_id, "ship", GoalType::Terminal)
1696 .with_trajectory(trajectory_id)
1697 .with_parent(parent_id)
1698 .with_criterion(criterion)
1699 .with_priority(5)
1700 .with_deadline(deadline);
1701
1702 assert_eq!(goal.agent_id, agent_id);
1703 assert_eq!(goal.trajectory_id, Some(trajectory_id));
1704 assert_eq!(goal.parent_goal_id, Some(parent_id));
1705 assert_eq!(goal.priority, 5);
1706 assert_eq!(goal.deadline, Some(deadline));
1707 assert_eq!(goal.status, GoalStatus::Pending);
1708 assert!(goal.started_at.is_none());
1709 assert!(goal.completed_at.is_none());
1710
1711 let before_start = chrono::Utc::now();
1712 goal.start();
1713 assert_eq!(goal.status, GoalStatus::Active);
1714 assert!(goal.started_at.is_some());
1715 assert!(
1716 goal.started_at
1717 .expect("started_at should be set after start")
1718 >= before_start
1719 );
1720
1721 goal.achieve();
1722 assert_eq!(goal.status, GoalStatus::Achieved);
1723 assert!(goal.completed_at.is_some());
1724
1725 let mut failed = AgentGoal::new(agent_id, "fail", GoalType::Milestone);
1726 failed.fail("nope");
1727 assert_eq!(failed.status, GoalStatus::Failed);
1728 assert_eq!(failed.failure_reason.as_deref(), Some("nope"));
1729 assert!(failed.completed_at.is_some());
1730 }
1731
1732 #[test]
1733 fn test_agent_goal_criteria_satisfaction() {
1734 let agent_id = AgentId::now_v7();
1735 let goal = AgentGoal::new(agent_id, "empty", GoalType::Terminal);
1736 assert!(goal.all_criteria_satisfied());
1737
1738 let mut unsatisfied = AgentGoal::new(agent_id, "needs work", GoalType::Subgoal)
1739 .with_criterion(SuccessCriterion::new("a"))
1740 .with_criterion(SuccessCriterion::new("b"));
1741 assert!(!unsatisfied.all_criteria_satisfied());
1742 unsatisfied.success_criteria[0].satisfied = true;
1743 unsatisfied.success_criteria[1].satisfied = true;
1744 assert!(unsatisfied.all_criteria_satisfied());
1745 }
1746
1747 #[test]
1748 fn test_plan_step_and_cost_builders() {
1749 let dep = StepId::now_v7();
1750 let step = PlanStep::new(1, "do", ActionType::Operation)
1751 .with_precondition("ready")
1752 .with_postcondition("done")
1753 .depends_on(dep);
1754
1755 assert_eq!(step.index, 1);
1756 assert_eq!(step.preconditions, vec!["ready".to_string()]);
1757 assert_eq!(step.postconditions, vec!["done".to_string()]);
1758 assert_eq!(step.depends_on, vec![dep]);
1759 assert_eq!(step.status, StepStatus::Pending);
1760
1761 let cost = PlanCost::new(100, 2500).with_monetary_cost(1.25);
1762 assert_eq!(cost.estimated_tokens, 100);
1763 assert_eq!(cost.estimated_duration_ms, 2500);
1764 assert_eq!(cost.monetary_cost_usd, Some(1.25));
1765 }
1766
1767 #[test]
1768 fn test_agent_plan_flow_and_next_step() {
1769 let agent_id = AgentId::now_v7();
1770 let goal_id = GoalId::now_v7();
1771 let mut plan = AgentPlan::new(agent_id, goal_id, "plan it");
1772 assert_eq!(plan.status, PlanStatus::Draft);
1773
1774 let mut step_ready = PlanStep::new(1, "prep", ActionType::Operation);
1775 step_ready.status = StepStatus::Ready;
1776 let step_pending = PlanStep::new(2, "execute", ActionType::ToolCall);
1777
1778 plan.add_step(step_ready.clone());
1779 plan.add_step(step_pending.clone());
1780
1781 let next = plan.next_step().expect("expected next step");
1782 assert_eq!(next.step_id, step_ready.step_id);
1783
1784 plan.ready();
1785 assert_eq!(plan.status, PlanStatus::Ready);
1786
1787 let before_start = chrono::Utc::now();
1788 plan.start();
1789 assert_eq!(plan.status, PlanStatus::InProgress);
1790 assert!(plan.started_at.is_some());
1791 assert!(
1792 plan.started_at
1793 .expect("started_at should be set after start")
1794 >= before_start
1795 );
1796
1797 let cost = PlanCost::new(10, 100);
1798 plan.complete(Some(cost.clone()));
1799 assert_eq!(plan.status, PlanStatus::Completed);
1800 assert_eq!(plan.actual_cost, Some(cost));
1801 assert!(plan.completed_at.is_some());
1802 }
1803
1804 #[test]
1805 fn test_backoff_strategy_delay() {
1806 let fixed = BackoffStrategy::Fixed { delay_ms: 100 };
1807 assert_eq!(fixed.delay_for_attempt(3), 100);
1808
1809 let linear = BackoffStrategy::Linear {
1810 base_ms: 50,
1811 increment_ms: 10,
1812 };
1813 assert_eq!(linear.delay_for_attempt(0), 50);
1814 assert_eq!(linear.delay_for_attempt(3), 80);
1815
1816 let exp = BackoffStrategy::Exponential {
1817 base_ms: 100,
1818 multiplier: 2.0,
1819 max_ms: 1000,
1820 };
1821 assert_eq!(exp.delay_for_attempt(0), 100);
1822 assert_eq!(exp.delay_for_attempt(3), 800);
1823 assert_eq!(exp.delay_for_attempt(4), 1000);
1824 }
1825
1826 #[test]
1827 fn test_retry_policy_default_values() {
1828 let policy = RetryPolicy::default();
1829 assert_eq!(policy.max_attempts, 3);
1830 assert_eq!(policy.timeout_per_attempt_ms, 30_000);
1831 match policy.backoff {
1832 BackoffStrategy::Exponential {
1833 base_ms,
1834 multiplier,
1835 max_ms,
1836 } => {
1837 assert_eq!(base_ms, 100);
1838 assert!((multiplier - 2.0).abs() < f64::EPSILON);
1839 assert_eq!(max_ms, 10_000);
1840 }
1841 _ => panic!("expected exponential backoff"),
1842 }
1843 }
1844
1845 #[test]
1846 fn test_agent_action_flow_and_retry() {
1847 let agent_id = AgentId::now_v7();
1848 let policy = RetryPolicy {
1849 max_attempts: 2,
1850 backoff: BackoffStrategy::None,
1851 timeout_per_attempt_ms: 1000,
1852 };
1853
1854 let mut action = AgentAction::new(agent_id, ActionType::Operation, "do")
1855 .with_retry_policy(policy)
1856 .with_parameters(json!({"k": "v"}))
1857 .with_timeout(500);
1858
1859 assert_eq!(action.status, ActionStatus::Pending);
1860 assert_eq!(action.attempt_count, 0);
1861 assert!(action.can_retry());
1862
1863 action.start();
1864 assert_eq!(action.status, ActionStatus::InProgress);
1865 assert_eq!(action.attempt_count, 1);
1866 let started_at = action.started_at;
1867
1868 action.start();
1869 assert_eq!(action.attempt_count, 2);
1870 assert_eq!(action.started_at, started_at);
1871
1872 action.complete();
1873 assert_eq!(action.status, ActionStatus::Completed);
1874 assert!(action.completed_at.is_some());
1875
1876 action.attempt_count = 2;
1877 assert!(!action.can_retry());
1878 }
1879
1880 #[test]
1881 fn test_agent_action_without_retry_policy() {
1882 let agent_id = AgentId::now_v7();
1883 let action = AgentAction::new(agent_id, ActionType::Operation, "do");
1884 assert!(!action.can_retry());
1885 }
1886
1887 #[test]
1888 fn test_success_criterion_defaults() {
1889 let criterion = SuccessCriterion::new("ok");
1890 assert_eq!(criterion.description, "ok");
1891 assert!(!criterion.measurable);
1892 assert!(criterion.target_value.is_none());
1893 assert!(criterion.current_value.is_none());
1894 assert!(!criterion.satisfied);
1895 }
1896
1897 #[test]
1898 fn test_belief_confidence_clamp_and_active() {
1899 let agent_id = AgentId::now_v7();
1900 let mut belief = Belief::new(
1901 agent_id,
1902 "fact",
1903 BeliefType::Fact,
1904 BeliefSource::Observation,
1905 )
1906 .with_confidence(2.5);
1907 assert_eq!(belief.confidence, 1.0);
1908
1909 let before_update = belief.updated_at;
1910 belief.update_confidence(-5.0);
1911 assert_eq!(belief.confidence, 0.0);
1912 assert!(belief.updated_at >= before_update);
1913
1914 let new_id = BeliefId::now_v7();
1915 belief.supersede(new_id);
1916 assert_eq!(belief.superseded_by, Some(new_id));
1917 assert!(!belief.is_active());
1918 }
1919
1920 #[test]
1921 fn test_agent_beliefs_add_and_active_filter() {
1922 let agent_id = AgentId::now_v7();
1923 let mut beliefs = AgentBeliefs::new(agent_id);
1924
1925 let fact = Belief::new(
1926 agent_id,
1927 "fact",
1928 BeliefType::Fact,
1929 BeliefSource::MemoryRecall,
1930 );
1931 let hypothesis = Belief::new(
1932 agent_id,
1933 "maybe",
1934 BeliefType::Hypothesis,
1935 BeliefSource::Inference,
1936 );
1937 let mut superseded =
1938 Belief::new(agent_id, "old", BeliefType::Fact, BeliefSource::Observation);
1939 superseded.supersede(BeliefId::now_v7());
1940
1941 beliefs.add(fact.clone());
1942 beliefs.add(hypothesis.clone());
1943 beliefs.add(superseded.clone());
1944
1945 assert_eq!(beliefs.facts.len(), 2);
1946 assert_eq!(beliefs.hypotheses.len(), 1);
1947 assert_eq!(beliefs.uncertainties.len(), 0);
1948
1949 let active: Vec<_> = beliefs.active_beliefs().collect();
1950 assert!(active.iter().all(|b| b.is_active()));
1951 assert_eq!(active.len(), 2);
1952 }
1953
1954 #[test]
1955 fn test_agent_observation_and_learning_builders() {
1956 let agent_id = AgentId::now_v7();
1957 let action_id = ActionId::now_v7();
1958 let belief_id = BeliefId::now_v7();
1959
1960 let learning = Learning::new(
1961 ObservationId::now_v7(),
1962 LearningType::PatternRecognition,
1963 "pattern",
1964 )
1965 .with_abstraction(AbstractionLevel::Summary)
1966 .with_applicability("global")
1967 .with_confidence(0.4);
1968
1969 let mut obs = AgentObservation::new(agent_id, action_id, true, 150)
1970 .with_result(json!({"ok": true}))
1971 .with_error("none")
1972 .with_tokens(42)
1973 .with_cost(0.25);
1974
1975 obs.add_belief_update(belief_id);
1976 obs.add_learning(learning);
1977
1978 assert_eq!(obs.agent_id, agent_id);
1979 assert_eq!(obs.action_id, action_id);
1980 assert_eq!(obs.duration_ms, 150);
1981 assert_eq!(obs.tokens_used, Some(42));
1982 assert_eq!(obs.cost_usd, Some(0.25));
1983 assert_eq!(obs.belief_updates, vec![belief_id]);
1984 assert_eq!(obs.learnings.len(), 1);
1985 assert!(obs.result.is_some());
1986 assert!(obs.error.is_some());
1987 }
1988
1989 #[test]
1990 fn test_message_type_parsing_variants_and_errors() {
1991 let underscored =
1992 MessageType::from_db_str("task_delegation").expect("underscore variant should parse");
1993 assert_eq!(underscored, MessageType::TaskDelegation);
1994
1995 let mixed = MessageType::from_db_str("Coordination_Signal")
1996 .expect("mixed case underscore variant should parse");
1997 assert_eq!(mixed, MessageType::CoordinationSignal);
1998
1999 let err = MessageType::from_db_str("unknown_type")
2000 .expect_err("unknown type should fail to parse");
2001 assert_eq!(err.input, "unknown_type");
2002 }
2003
2004 #[test]
2005 fn test_message_priority_parsing_variants_and_errors() {
2006 let high = MessagePriority::from_db_str("HIGH").expect("uppercase should parse");
2007 assert_eq!(high, MessagePriority::High);
2008
2009 let normal = MessagePriority::from_db_str("normal").expect("lowercase should parse");
2010 assert_eq!(normal, MessagePriority::Normal);
2011
2012 let err = MessagePriority::from_db_str("urgent")
2013 .expect_err("unknown priority should fail to parse");
2014 assert_eq!(err.input, "urgent");
2015 }
2016
2017 #[test]
2018 fn test_handoff_reason_parsing_aliases() {
2019 let failed =
2020 HandoffReason::from_db_str("failed").expect("failed alias should parse to Failure");
2021 assert_eq!(failed, HandoffReason::Failure);
2022
2023 let load =
2024 HandoffReason::from_db_str("load_balancing").expect("underscore variant should parse");
2025 assert_eq!(load, HandoffReason::LoadBalancing);
2026
2027 let err = HandoffReason::from_db_str("nonsense")
2028 .expect_err("unknown reason should fail to parse");
2029 assert_eq!(err.input, "nonsense");
2030 }
2031
2032 #[test]
2033 fn test_memory_access_default_permissions() {
2034 let access = MemoryAccess::default();
2035 assert_eq!(access.read.len(), 1);
2036 assert_eq!(access.write.len(), 1);
2037 assert_eq!(access.read[0].memory_type, crate::MemoryType::All);
2038 assert_eq!(access.write[0].memory_type, crate::MemoryType::All);
2039 assert_eq!(access.read[0].scope, PermissionScope::Own);
2040 assert_eq!(access.write[0].scope, PermissionScope::Own);
2041 assert!(access.read[0].filter.is_none());
2042 assert!(access.write[0].filter.is_none());
2043 }
2044}