1use crate::agent::AgentTarget;
4use crate::{
5 identity::EntityIdType,
6 AbstractionLevel,
7 AgentId,
8 AgentStatus,
10 AgentType,
11 ArtifactId,
12 ArtifactType,
13 ConflictId,
14 ConflictStatus,
15 ConflictType,
16 ContentHash,
17 DelegationId,
18 DelegationResult,
19 DelegationResultStatus,
20 DelegationStatus,
21 EmbeddingVector,
22 EntityType,
24 ExtractionMethod,
25 HandoffId,
26 HandoffReason,
27 HandoffStatus,
28 MemoryAccess,
29 MessageId,
30 MessagePriority,
31 MessageType,
32 NoteId,
33 NoteType,
34 OutcomeStatus,
35 PrincipalId,
36 PrincipalType,
37 RawContent,
38 ResolutionStrategy,
39 ScopeId,
40 TeamId,
41 TenantId,
42 Timestamp,
43 TrajectoryId,
45 TrajectoryStatus,
46 TurnId,
47 TurnRole,
48 WorkingSetId,
49 TTL,
50};
51use chrono::Utc;
52use serde::{Deserialize, Serialize};
53use uuid::Uuid;
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
57#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
58pub struct EntityRef {
59 pub entity_type: EntityType,
60 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
61 pub id: Uuid, }
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
68pub struct Trajectory {
69 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
70 pub trajectory_id: TrajectoryId,
71 pub name: String,
72 pub description: Option<String>,
73 pub status: TrajectoryStatus,
74 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
75 pub parent_trajectory_id: Option<TrajectoryId>,
76 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
77 pub root_trajectory_id: Option<TrajectoryId>,
78 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
79 pub agent_id: Option<AgentId>,
80 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
81 pub created_at: Timestamp,
82 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
83 pub updated_at: Timestamp,
84 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
85 pub completed_at: Option<Timestamp>,
86 pub outcome: Option<TrajectoryOutcome>,
87 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
88 pub metadata: Option<serde_json::Value>,
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
94pub struct TrajectoryOutcome {
95 pub status: OutcomeStatus,
96 pub summary: String,
97 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
98 pub produced_artifacts: Vec<ArtifactId>,
99 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
100 pub produced_notes: Vec<NoteId>,
101 pub error: Option<String>,
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
108pub struct Scope {
109 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
110 pub scope_id: ScopeId,
111 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
112 pub trajectory_id: TrajectoryId,
113 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
114 pub parent_scope_id: Option<ScopeId>,
115 pub name: String,
116 pub purpose: Option<String>,
117 pub is_active: bool,
118 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
119 pub created_at: Timestamp,
120 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
121 pub closed_at: Option<Timestamp>,
122 pub checkpoint: Option<Checkpoint>,
123 pub token_budget: i32,
124 pub tokens_used: i32,
125 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
126 pub metadata: Option<serde_json::Value>,
127}
128
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
132pub struct Checkpoint {
133 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "byte"))]
134 pub context_state: RawContent,
135 pub recoverable: bool,
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
142pub struct Artifact {
143 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
144 pub artifact_id: ArtifactId,
145 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
146 pub trajectory_id: TrajectoryId,
147 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
148 pub scope_id: ScopeId,
149 pub artifact_type: ArtifactType,
150 pub name: String,
151 pub content: String,
152 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "byte"))]
153 pub content_hash: ContentHash,
154 pub embedding: Option<EmbeddingVector>,
155 pub provenance: Provenance,
156 pub ttl: TTL,
157 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
158 pub created_at: Timestamp,
159 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
160 pub updated_at: Timestamp,
161 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
162 pub superseded_by: Option<ArtifactId>,
163 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
164 pub metadata: Option<serde_json::Value>,
165}
166
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
172#[serde(tag = "type", rename_all = "snake_case")]
173pub enum ArtifactMetadata {
174 Audio {
176 duration_secs: f32,
178 format: String,
180 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
182 transcript_id: Option<ArtifactId>,
183 },
184 Image {
186 width: u32,
188 height: u32,
190 format: String,
192 alt_text: Option<String>,
194 },
195 Video {
197 duration_secs: f32,
199 format: String,
201 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
203 thumbnail_id: Option<ArtifactId>,
204 },
205 Transcript {
207 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
209 source_artifact_id: ArtifactId,
210 confidence: f32,
212 language: Option<String>,
214 },
215 Screenshot {
217 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
219 captured_at: Timestamp,
220 context: String,
222 width: Option<u32>,
224 height: Option<u32>,
226 },
227}
228
229impl ArtifactMetadata {
230 pub fn to_json(&self) -> serde_json::Value {
232 serde_json::to_value(self).unwrap_or_default()
233 }
234
235 pub fn from_json(value: &serde_json::Value) -> Option<Self> {
237 serde_json::from_value(value.clone()).ok()
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
243#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
244pub struct Provenance {
245 pub source_turn: i32,
246 pub extraction_method: ExtractionMethod,
247 pub confidence: Option<f32>,
248}
249
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
254pub struct Note {
255 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
256 pub note_id: NoteId,
257 pub note_type: NoteType,
258 pub title: String,
259 pub content: String,
260 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "byte"))]
261 pub content_hash: ContentHash,
262 pub embedding: Option<EmbeddingVector>,
263 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
264 pub source_trajectory_ids: Vec<TrajectoryId>,
265 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
266 pub source_artifact_ids: Vec<ArtifactId>,
267 pub ttl: TTL,
268 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
269 pub created_at: Timestamp,
270 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
271 pub updated_at: Timestamp,
272 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
273 pub accessed_at: Timestamp,
274 pub access_count: i32,
275 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
276 pub superseded_by: Option<NoteId>,
277 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
278 pub metadata: Option<serde_json::Value>,
279 pub abstraction_level: AbstractionLevel,
284 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
286 pub source_note_ids: Vec<NoteId>,
287}
288
289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
293pub struct Turn {
294 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
295 pub turn_id: TurnId,
296 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
297 pub scope_id: ScopeId,
298 pub sequence: i32,
299 pub role: TurnRole,
300 pub content: String,
301 pub token_count: i32,
302 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
303 pub created_at: Timestamp,
304 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
305 pub tool_calls: Option<serde_json::Value>,
306 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
307 pub tool_results: Option<serde_json::Value>,
308 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
309 pub metadata: Option<serde_json::Value>,
310}
311
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
319pub struct Principal {
320 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
321 pub principal_id: PrincipalId,
322 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
323 pub tenant_id: TenantId,
324 pub principal_type: PrincipalType,
325 pub user_id: Option<String>,
326 pub display_name: Option<String>,
327 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
328 pub metadata: Option<serde_json::Value>,
329 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
330 pub created_at: Timestamp,
331 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
332 pub updated_at: Timestamp,
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
338pub struct Agent {
339 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
341 pub agent_id: AgentId,
342 pub agent_type: AgentType,
344 pub capabilities: Vec<String>,
346 pub memory_access: MemoryAccess,
348
349 pub status: AgentStatus,
351 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
353 pub current_trajectory_id: Option<TrajectoryId>,
354 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
356 pub current_scope_id: Option<ScopeId>,
357
358 pub can_delegate_to: Vec<String>,
360 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
362 pub reports_to: Option<AgentId>,
363 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
365 pub owner_principal_id: PrincipalId,
366
367 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
369 pub created_at: Timestamp,
370 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
372 pub last_heartbeat_at: Timestamp,
373}
374
375impl Agent {
376 pub fn new(agent_type: impl Into<AgentType>, capabilities: Vec<String>) -> Self {
378 let now = Utc::now();
379 Self {
380 agent_id: AgentId::new(Uuid::now_v7()),
381 agent_type: agent_type.into(),
382 capabilities,
383 memory_access: MemoryAccess::default(),
384 status: AgentStatus::Idle,
385 current_trajectory_id: None,
386 current_scope_id: None,
387 can_delegate_to: Vec::new(),
388 reports_to: None,
389 owner_principal_id: PrincipalId::nil(),
390 created_at: now,
391 last_heartbeat_at: now,
392 }
393 }
394
395 pub fn with_memory_access(mut self, access: MemoryAccess) -> Self {
397 self.memory_access = access;
398 self
399 }
400
401 pub fn with_delegation_targets(mut self, targets: Vec<String>) -> Self {
403 self.can_delegate_to = targets;
404 self
405 }
406
407 pub fn with_supervisor(mut self, supervisor_id: AgentId) -> Self {
409 self.reports_to = Some(supervisor_id);
410 self
411 }
412
413 pub fn with_owner_principal(mut self, owner_principal_id: PrincipalId) -> Self {
415 self.owner_principal_id = owner_principal_id;
416 self
417 }
418
419 pub fn heartbeat(&mut self) {
421 self.last_heartbeat_at = Utc::now();
422 }
423
424 pub fn has_capability(&self, capability: &str) -> bool {
426 self.capabilities.iter().any(|c| c == capability)
427 }
428
429 pub fn can_delegate_to_type(&self, agent_type: &AgentType) -> bool {
431 self.can_delegate_to
432 .iter()
433 .any(|t| t == agent_type.as_str())
434 }
435}
436
437#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
439#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
440pub struct AgentWorkingSetEntry {
441 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
442 pub working_set_id: WorkingSetId,
443 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
444 pub tenant_id: TenantId,
445 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
446 pub agent_id: AgentId,
447 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
448 pub owner_principal_id: PrincipalId,
449 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
450 pub scope_id: Option<ScopeId>,
451 pub key: String,
452 #[cfg_attr(feature = "openapi", schema(value_type = Object))]
453 pub value: serde_json::Value,
454 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
455 pub expires_at: Option<Timestamp>,
456 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
457 pub updated_at: Timestamp,
458 pub version: i64,
459 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
460 pub metadata: Option<serde_json::Value>,
461}
462
463#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
465#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
466pub struct AgentMessage {
467 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
468 pub message_id: MessageId,
469 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
470 pub from_agent_id: AgentId,
471 pub to: Option<AgentTarget>,
473 pub message_type: MessageType,
474 pub payload: String,
475 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
476 pub trajectory_id: Option<TrajectoryId>,
477 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
478 pub scope_id: Option<ScopeId>,
479 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
480 pub artifact_ids: Vec<ArtifactId>,
481 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
482 pub created_at: Timestamp,
483 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
484 pub delivered_at: Option<Timestamp>,
485 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
486 pub acknowledged_at: Option<Timestamp>,
487 pub priority: MessagePriority,
488 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
489 pub expires_at: Option<Timestamp>,
490}
491
492impl AgentMessage {
493 pub fn to_agent(from: AgentId, to: AgentId, msg_type: MessageType, payload: &str) -> Self {
495 Self {
496 message_id: MessageId::new(Uuid::now_v7()),
497 from_agent_id: from,
498 to: Some(AgentTarget::ById(to)),
499 message_type: msg_type,
500 payload: payload.to_string(),
501 trajectory_id: None,
502 scope_id: None,
503 artifact_ids: Vec::new(),
504 created_at: Utc::now(),
505 delivered_at: None,
506 acknowledged_at: None,
507 priority: MessagePriority::Normal,
508 expires_at: None,
509 }
510 }
511
512 pub fn to_type(
514 from: AgentId,
515 agent_type: impl Into<AgentType>,
516 msg_type: MessageType,
517 payload: &str,
518 ) -> Self {
519 Self {
520 message_id: MessageId::new(Uuid::now_v7()),
521 from_agent_id: from,
522 to: Some(AgentTarget::ByType(agent_type.into())),
523 message_type: msg_type,
524 payload: payload.to_string(),
525 trajectory_id: None,
526 scope_id: None,
527 artifact_ids: Vec::new(),
528 created_at: Utc::now(),
529 delivered_at: None,
530 acknowledged_at: None,
531 priority: MessagePriority::Normal,
532 expires_at: None,
533 }
534 }
535
536 pub fn with_trajectory(mut self, trajectory_id: TrajectoryId) -> Self {
538 self.trajectory_id = Some(trajectory_id);
539 self
540 }
541
542 pub fn with_scope(mut self, scope_id: ScopeId) -> Self {
544 self.scope_id = Some(scope_id);
545 self
546 }
547
548 pub fn with_artifacts(mut self, artifacts: Vec<ArtifactId>) -> Self {
550 self.artifact_ids = artifacts;
551 self
552 }
553
554 pub fn with_priority(mut self, priority: MessagePriority) -> Self {
556 self.priority = priority;
557 self
558 }
559
560 pub fn mark_delivered(&mut self) {
562 self.delivered_at = Some(Utc::now());
563 }
564
565 pub fn mark_acknowledged(&mut self) {
567 self.acknowledged_at = Some(Utc::now());
568 }
569
570 pub fn is_for_agent(&self, agent_id: AgentId) -> bool {
572 self.to.as_ref().and_then(AgentTarget::as_agent_id) == Some(agent_id)
573 }
574}
575
576#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
578#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
579pub struct DelegatedTask {
580 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
581 pub delegation_id: DelegationId,
582 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
583 pub delegator_agent_id: AgentId,
584 pub delegatee: Option<AgentTarget>,
586 pub task_description: String,
587 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
588 pub parent_trajectory_id: TrajectoryId,
589 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
590 pub child_trajectory_id: Option<TrajectoryId>,
591 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
592 pub shared_artifacts: Vec<ArtifactId>,
593 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
594 pub shared_notes: Vec<NoteId>,
595 pub additional_context: Option<String>,
596 #[cfg_attr(feature = "openapi", schema(value_type = Option<Object>))]
597 pub constraints: Option<serde_json::Value>,
598 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
599 pub deadline: Option<Timestamp>,
600 pub status: DelegationStatus,
601 pub result: Option<DelegationResult>,
602 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
603 pub created_at: Timestamp,
604 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
605 pub accepted_at: Option<Timestamp>,
606 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
607 pub completed_at: Option<Timestamp>,
608}
609
610impl DelegatedTask {
611 pub fn to_agent(
613 from: AgentId,
614 to: AgentId,
615 trajectory: TrajectoryId,
616 description: &str,
617 ) -> Self {
618 Self {
619 delegation_id: DelegationId::new(Uuid::now_v7()),
620 delegator_agent_id: from,
621 delegatee: Some(AgentTarget::ById(to)),
622 task_description: description.to_string(),
623 parent_trajectory_id: trajectory,
624 child_trajectory_id: None,
625 shared_artifacts: Vec::new(),
626 shared_notes: Vec::new(),
627 additional_context: None,
628 constraints: None,
629 deadline: None,
630 status: DelegationStatus::Pending,
631 result: None,
632 created_at: Utc::now(),
633 accepted_at: None,
634 completed_at: None,
635 }
636 }
637
638 pub fn to_type(
640 from: AgentId,
641 agent_type: impl Into<AgentType>,
642 trajectory: TrajectoryId,
643 description: &str,
644 ) -> Self {
645 Self {
646 delegation_id: DelegationId::new(Uuid::now_v7()),
647 delegator_agent_id: from,
648 delegatee: Some(AgentTarget::ByType(agent_type.into())),
649 task_description: description.to_string(),
650 parent_trajectory_id: trajectory,
651 child_trajectory_id: None,
652 shared_artifacts: Vec::new(),
653 shared_notes: Vec::new(),
654 additional_context: None,
655 constraints: None,
656 deadline: None,
657 status: DelegationStatus::Pending,
658 result: None,
659 created_at: Utc::now(),
660 accepted_at: None,
661 completed_at: None,
662 }
663 }
664
665 pub fn with_shared_artifacts(mut self, artifacts: Vec<ArtifactId>) -> Self {
667 self.shared_artifacts = artifacts;
668 self
669 }
670
671 pub fn with_shared_notes(mut self, notes: Vec<NoteId>) -> Self {
673 self.shared_notes = notes;
674 self
675 }
676
677 pub fn with_deadline(mut self, deadline: Timestamp) -> Self {
679 self.deadline = Some(deadline);
680 self
681 }
682
683 pub fn accept(&mut self) {
685 self.status = DelegationStatus::Accepted;
686 self.accepted_at = Some(Utc::now());
687 }
688
689 pub fn complete(&mut self, result: DelegationResult) {
691 self.status = if result.status == DelegationResultStatus::Failure {
692 DelegationStatus::Failed
693 } else {
694 DelegationStatus::Completed
695 };
696 self.result = Some(result);
697 self.completed_at = Some(Utc::now());
698 }
699}
700
701#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
703#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
704pub struct AgentHandoff {
705 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
706 pub handoff_id: HandoffId,
707 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
708 pub from_agent_id: AgentId,
709 pub to: Option<AgentTarget>,
711 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
712 pub trajectory_id: TrajectoryId,
713 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
714 pub scope_id: ScopeId,
715 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
716 pub context_snapshot_id: Option<ArtifactId>,
717 pub handoff_notes: Option<String>,
718 pub next_steps: Vec<String>,
719 pub blockers: Vec<String>,
720 pub open_questions: Vec<String>,
721 pub status: HandoffStatus,
722 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
723 pub initiated_at: Timestamp,
724 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
725 pub accepted_at: Option<Timestamp>,
726 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
727 pub completed_at: Option<Timestamp>,
728 pub reason: HandoffReason,
729}
730
731impl AgentHandoff {
732 pub fn to_agent(
734 from: AgentId,
735 to: AgentId,
736 trajectory: TrajectoryId,
737 scope: ScopeId,
738 reason: HandoffReason,
739 ) -> Self {
740 Self {
741 handoff_id: HandoffId::new(Uuid::now_v7()),
742 from_agent_id: from,
743 to: Some(AgentTarget::ById(to)),
744 trajectory_id: trajectory,
745 scope_id: scope,
746 context_snapshot_id: None,
747 handoff_notes: None,
748 next_steps: Vec::new(),
749 blockers: Vec::new(),
750 open_questions: Vec::new(),
751 status: HandoffStatus::Initiated,
752 initiated_at: Utc::now(),
753 accepted_at: None,
754 completed_at: None,
755 reason,
756 }
757 }
758
759 pub fn to_type(
761 from: AgentId,
762 agent_type: impl Into<AgentType>,
763 trajectory: TrajectoryId,
764 scope: ScopeId,
765 reason: HandoffReason,
766 ) -> Self {
767 Self {
768 handoff_id: HandoffId::new(Uuid::now_v7()),
769 from_agent_id: from,
770 to: Some(AgentTarget::ByType(agent_type.into())),
771 trajectory_id: trajectory,
772 scope_id: scope,
773 context_snapshot_id: None,
774 handoff_notes: None,
775 next_steps: Vec::new(),
776 blockers: Vec::new(),
777 open_questions: Vec::new(),
778 status: HandoffStatus::Initiated,
779 initiated_at: Utc::now(),
780 accepted_at: None,
781 completed_at: None,
782 reason,
783 }
784 }
785
786 pub fn with_notes(mut self, notes: &str) -> Self {
788 self.handoff_notes = Some(notes.to_string());
789 self
790 }
791
792 pub fn with_next_steps(mut self, steps: Vec<String>) -> Self {
794 self.next_steps = steps;
795 self
796 }
797
798 pub fn accept(&mut self) {
800 self.status = HandoffStatus::Accepted;
801 self.accepted_at = Some(Utc::now());
802 }
803
804 pub fn complete(&mut self) {
806 self.status = HandoffStatus::Completed;
807 self.completed_at = Some(Utc::now());
808 }
809}
810
811#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
813#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
814pub struct Conflict {
815 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
816 pub conflict_id: ConflictId,
817 pub conflict_type: ConflictType,
818 pub item_a_type: EntityType,
819 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
820 pub item_a_id: Uuid,
821 pub item_b_type: EntityType,
822 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
823 pub item_b_id: Uuid,
824 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
825 pub agent_a_id: Option<AgentId>,
826 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
827 pub agent_b_id: Option<AgentId>,
828 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
829 pub trajectory_id: Option<TrajectoryId>,
830 pub status: ConflictStatus,
831 pub resolution: Option<ConflictResolutionRecord>,
832 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
833 pub detected_at: Timestamp,
834 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "date-time"))]
835 pub resolved_at: Option<Timestamp>,
836}
837
838impl Conflict {
839 pub fn new(
841 conflict_type: ConflictType,
842 item_a_type: EntityType,
843 item_a_id: Uuid,
844 item_b_type: EntityType,
845 item_b_id: Uuid,
846 ) -> Self {
847 Self {
848 conflict_id: ConflictId::new(Uuid::now_v7()),
849 conflict_type,
850 item_a_type,
851 item_a_id,
852 item_b_type,
853 item_b_id,
854 agent_a_id: None,
855 agent_b_id: None,
856 trajectory_id: None,
857 status: ConflictStatus::Detected,
858 resolution: None,
859 detected_at: Utc::now(),
860 resolved_at: None,
861 }
862 }
863
864 pub fn with_agents(mut self, agent_a: AgentId, agent_b: AgentId) -> Self {
866 self.agent_a_id = Some(agent_a);
867 self.agent_b_id = Some(agent_b);
868 self
869 }
870
871 pub fn with_trajectory(mut self, trajectory_id: TrajectoryId) -> Self {
873 self.trajectory_id = Some(trajectory_id);
874 self
875 }
876
877 pub fn resolve(&mut self, resolution: ConflictResolutionRecord) {
879 self.status = ConflictStatus::Resolved;
880 self.resolution = Some(resolution);
881 self.resolved_at = Some(Utc::now());
882 }
883}
884
885#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
887#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
888pub struct ConflictResolutionRecord {
889 pub strategy: ResolutionStrategy,
890 pub winner: Option<String>,
891 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
892 pub merged_result_id: Option<Uuid>,
893 pub reason: String,
894 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "uuid"))]
895 pub resolved_by: Option<AgentId>,
896}
897
898impl ConflictResolutionRecord {
899 pub fn automatic(strategy: ResolutionStrategy, reason: &str) -> Self {
901 Self {
902 strategy,
903 winner: None,
904 merged_result_id: None,
905 reason: reason.to_string(),
906 resolved_by: None,
907 }
908 }
909
910 pub fn manual(strategy: ResolutionStrategy, reason: &str, resolved_by: AgentId) -> Self {
912 Self {
913 strategy,
914 winner: None,
915 merged_result_id: None,
916 reason: reason.to_string(),
917 resolved_by: Some(resolved_by),
918 }
919 }
920}
921
922#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
928#[serde(rename_all = "snake_case")]
929#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
930pub enum TeamRole {
931 Owner,
933 Admin,
935 Member,
937}
938
939impl std::fmt::Display for TeamRole {
940 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
941 let s = match self {
942 TeamRole::Owner => "owner",
943 TeamRole::Admin => "admin",
944 TeamRole::Member => "member",
945 };
946 write!(f, "{}", s)
947 }
948}
949
950impl std::str::FromStr for TeamRole {
951 type Err = String;
952
953 fn from_str(s: &str) -> Result<Self, Self::Err> {
954 match s.trim().to_ascii_lowercase().as_str() {
955 "owner" => Ok(TeamRole::Owner),
956 "admin" => Ok(TeamRole::Admin),
957 "member" => Ok(TeamRole::Member),
958 _ => Err(format!("Invalid TeamRole: {}", s)),
959 }
960 }
961}
962
963#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
965#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
966pub struct Team {
967 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
969 pub team_id: TeamId,
970 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
972 pub tenant_id: TenantId,
973 pub name: String,
975 #[serde(skip_serializing_if = "Option::is_none")]
977 pub description: Option<String>,
978 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
980 pub created_at: Timestamp,
981 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
983 pub updated_at: Timestamp,
984}
985
986#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
988#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
989pub struct TeamMember {
990 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
992 pub team_id: TeamId,
993 pub user_id: String,
995 pub role: TeamRole,
997 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "date-time"))]
999 pub joined_at: Timestamp,
1000}
1001
1002#[cfg(test)]
1007mod tests {
1008 use super::*;
1009 use serde_json::json;
1010
1011 fn sample_hash() -> ContentHash {
1012 ContentHash::new([7u8; 32])
1013 }
1014
1015 fn sample_embedding() -> EmbeddingVector {
1016 EmbeddingVector::new(vec![0.0, 1.0, 0.5], "test-model".to_string())
1017 }
1018
1019 #[test]
1020 fn test_entity_ref_serde_roundtrip() {
1021 let entity = EntityRef {
1022 entity_type: EntityType::Trajectory,
1023 id: Uuid::now_v7(),
1024 };
1025 let json = serde_json::to_string(&entity).expect("EntityRef serialization should succeed");
1026 let restored: EntityRef =
1027 serde_json::from_str(&json).expect("EntityRef deserialization should succeed");
1028 assert_eq!(entity, restored);
1029 }
1030
1031 #[test]
1032 fn test_trajectory_serde_roundtrip() {
1033 let outcome = TrajectoryOutcome {
1034 status: OutcomeStatus::Success,
1035 summary: "done".to_string(),
1036 produced_artifacts: vec![ArtifactId::now_v7()],
1037 produced_notes: vec![NoteId::now_v7()],
1038 error: None,
1039 };
1040 let trajectory = Trajectory {
1041 trajectory_id: TrajectoryId::now_v7(),
1042 name: "test".to_string(),
1043 description: Some("desc".to_string()),
1044 status: TrajectoryStatus::Active,
1045 parent_trajectory_id: None,
1046 root_trajectory_id: None,
1047 agent_id: Some(AgentId::now_v7()),
1048 created_at: Utc::now(),
1049 updated_at: Utc::now(),
1050 completed_at: None,
1051 outcome: Some(outcome),
1052 metadata: Some(json!({"k": "v"})),
1053 };
1054 let json =
1055 serde_json::to_string(&trajectory).expect("Trajectory serialization should succeed");
1056 let restored: Trajectory =
1057 serde_json::from_str(&json).expect("Trajectory deserialization should succeed");
1058 assert_eq!(trajectory, restored);
1059 }
1060
1061 #[test]
1062 fn test_scope_serde_roundtrip() {
1063 let scope = Scope {
1064 scope_id: ScopeId::now_v7(),
1065 trajectory_id: TrajectoryId::now_v7(),
1066 parent_scope_id: None,
1067 name: "scope".to_string(),
1068 purpose: Some("purpose".to_string()),
1069 is_active: true,
1070 created_at: Utc::now(),
1071 closed_at: None,
1072 checkpoint: Some(Checkpoint {
1073 context_state: vec![1, 2, 3],
1074 recoverable: true,
1075 }),
1076 token_budget: 8000,
1077 tokens_used: 42,
1078 metadata: Some(json!({"a": 1})),
1079 };
1080 let json = serde_json::to_string(&scope).expect("Scope serialization should succeed");
1081 let restored: Scope =
1082 serde_json::from_str(&json).expect("Scope deserialization should succeed");
1083 assert_eq!(scope, restored);
1084 }
1085
1086 #[test]
1087 fn test_artifact_serde_roundtrip() {
1088 let artifact = Artifact {
1089 artifact_id: ArtifactId::now_v7(),
1090 trajectory_id: TrajectoryId::now_v7(),
1091 scope_id: ScopeId::now_v7(),
1092 artifact_type: ArtifactType::Fact,
1093 name: "artifact".to_string(),
1094 content: "content".to_string(),
1095 content_hash: sample_hash(),
1096 embedding: Some(sample_embedding()),
1097 provenance: Provenance {
1098 source_turn: 1,
1099 extraction_method: ExtractionMethod::Explicit,
1100 confidence: Some(1.0),
1101 },
1102 ttl: TTL::Persistent,
1103 created_at: Utc::now(),
1104 updated_at: Utc::now(),
1105 superseded_by: None,
1106 metadata: Some(json!({"meta": true})),
1107 };
1108 let json = serde_json::to_string(&artifact).expect("Artifact serialization should succeed");
1109 let restored: Artifact =
1110 serde_json::from_str(&json).expect("Artifact deserialization should succeed");
1111 assert_eq!(artifact, restored);
1112 }
1113
1114 #[test]
1115 fn test_note_serde_roundtrip() {
1116 let note = Note {
1117 note_id: NoteId::now_v7(),
1118 note_type: NoteType::Insight,
1119 title: "title".to_string(),
1120 content: "content".to_string(),
1121 content_hash: sample_hash(),
1122 embedding: Some(sample_embedding()),
1123 source_trajectory_ids: vec![TrajectoryId::now_v7()],
1124 source_artifact_ids: vec![ArtifactId::now_v7()],
1125 ttl: TTL::Persistent,
1126 created_at: Utc::now(),
1127 updated_at: Utc::now(),
1128 accessed_at: Utc::now(),
1129 access_count: 2,
1130 superseded_by: None,
1131 metadata: Some(json!({"n": 1})),
1132 abstraction_level: AbstractionLevel::Raw,
1133 source_note_ids: vec![NoteId::now_v7()],
1134 };
1135 let json = serde_json::to_string(¬e).expect("Note serialization should succeed");
1136 let restored: Note =
1137 serde_json::from_str(&json).expect("Note deserialization should succeed");
1138 assert_eq!(note, restored);
1139 }
1140
1141 #[test]
1142 fn test_turn_serde_roundtrip() {
1143 let turn = Turn {
1144 turn_id: TurnId::now_v7(),
1145 scope_id: ScopeId::now_v7(),
1146 sequence: 1,
1147 role: TurnRole::User,
1148 content: "hi".to_string(),
1149 token_count: 10,
1150 created_at: Utc::now(),
1151 tool_calls: Some(json!({"tool": "search"})),
1152 tool_results: Some(json!({"result": "ok"})),
1153 metadata: Some(json!({"m": true})),
1154 };
1155 let json = serde_json::to_string(&turn).expect("Turn serialization should succeed");
1156 let restored: Turn =
1157 serde_json::from_str(&json).expect("Turn deserialization should succeed");
1158 assert_eq!(turn, restored);
1159 }
1160
1161 #[test]
1162 fn test_agent_builder_and_helpers() {
1163 let mut agent = Agent::new("coder", vec!["code".to_string(), "review".to_string()]);
1164 assert_eq!(agent.status, AgentStatus::Idle);
1165 assert!(agent.has_capability("code"));
1166 assert!(!agent.has_capability("design"));
1167
1168 let access = MemoryAccess::default();
1169 agent = agent.with_memory_access(access.clone());
1170 assert_eq!(agent.memory_access, access);
1171
1172 agent = agent.with_delegation_targets(vec!["planner".to_string()]);
1173 assert!(agent.can_delegate_to_type(&crate::AgentType::Planner));
1174
1175 let supervisor = AgentId::now_v7();
1176 agent = agent.with_supervisor(supervisor);
1177 assert_eq!(agent.reports_to, Some(supervisor));
1178
1179 let before = agent.last_heartbeat_at;
1180 agent.heartbeat();
1181 assert!(agent.last_heartbeat_at >= before);
1182 }
1183
1184 #[test]
1185 fn test_agent_message_builders_and_state() {
1186 let from = AgentId::now_v7();
1187 let to = AgentId::now_v7();
1188 let mut msg = AgentMessage::to_agent(from, to, MessageType::TaskDelegation, "do it")
1189 .with_trajectory(TrajectoryId::now_v7())
1190 .with_scope(ScopeId::now_v7())
1191 .with_artifacts(vec![ArtifactId::now_v7()])
1192 .with_priority(MessagePriority::High);
1193
1194 assert_eq!(msg.to.as_ref().and_then(AgentTarget::as_agent_id), Some(to));
1195 assert!(msg
1196 .to
1197 .as_ref()
1198 .and_then(AgentTarget::as_agent_type)
1199 .is_none());
1200 assert!(msg.is_for_agent(to));
1201 assert_eq!(msg.priority, MessagePriority::High);
1202
1203 msg.mark_delivered();
1204 assert!(msg.delivered_at.is_some());
1205 msg.mark_acknowledged();
1206 assert!(msg.acknowledged_at.is_some());
1207
1208 let by_type = AgentMessage::to_type(from, "planner", MessageType::Heartbeat, "ping");
1209 assert!(by_type
1210 .to
1211 .as_ref()
1212 .and_then(AgentTarget::as_agent_id)
1213 .is_none());
1214 assert_eq!(
1215 by_type.to.as_ref().and_then(AgentTarget::as_agent_type),
1216 Some(&AgentType::Planner)
1217 );
1218 }
1219
1220 #[test]
1221 fn test_delegated_task_lifecycle() {
1222 let from = AgentId::now_v7();
1223 let to = AgentId::now_v7();
1224 let trajectory = TrajectoryId::now_v7();
1225 let mut task = DelegatedTask::to_agent(from, to, trajectory, "do it")
1226 .with_shared_artifacts(vec![ArtifactId::now_v7()])
1227 .with_shared_notes(vec![NoteId::now_v7()]);
1228
1229 assert_eq!(task.status, DelegationStatus::Pending);
1230 assert!(task.accepted_at.is_none());
1231 task.accept();
1232 assert_eq!(task.status, DelegationStatus::Accepted);
1233 assert!(task.accepted_at.is_some());
1234
1235 let result = DelegationResult::success("ok", vec![]);
1236 task.complete(result.clone());
1237 assert_eq!(task.status, DelegationStatus::Completed);
1238 assert_eq!(task.result, Some(result));
1239 assert!(task.completed_at.is_some());
1240
1241 let mut failed = DelegatedTask::to_type(from, "planner", trajectory, "fail");
1242 let fail_result = DelegationResult::failure("bad");
1243 failed.complete(fail_result.clone());
1244 assert_eq!(failed.status, DelegationStatus::Failed);
1245 assert_eq!(failed.result, Some(fail_result));
1246 }
1247
1248 #[test]
1249 fn test_handoff_lifecycle() {
1250 let from = AgentId::now_v7();
1251 let to = AgentId::now_v7();
1252 let trajectory = TrajectoryId::now_v7();
1253 let scope = ScopeId::now_v7();
1254 let mut handoff =
1255 AgentHandoff::to_agent(from, to, trajectory, scope, HandoffReason::Timeout)
1256 .with_notes("note")
1257 .with_next_steps(vec!["step1".to_string()]);
1258
1259 assert_eq!(handoff.status, HandoffStatus::Initiated);
1260 assert_eq!(handoff.handoff_notes.as_deref(), Some("note"));
1261 assert_eq!(handoff.next_steps.len(), 1);
1262
1263 handoff.accept();
1264 assert_eq!(handoff.status, HandoffStatus::Accepted);
1265 assert!(handoff.accepted_at.is_some());
1266
1267 handoff.complete();
1268 assert_eq!(handoff.status, HandoffStatus::Completed);
1269 assert!(handoff.completed_at.is_some());
1270
1271 let by_type =
1272 AgentHandoff::to_type(from, "planner", trajectory, scope, HandoffReason::Failure);
1273 assert!(by_type
1274 .to
1275 .as_ref()
1276 .and_then(AgentTarget::as_agent_id)
1277 .is_none());
1278 assert_eq!(
1279 by_type.to.as_ref().and_then(AgentTarget::as_agent_type),
1280 Some(&AgentType::Planner)
1281 );
1282 }
1283
1284 #[test]
1285 fn test_conflict_resolution_flow() {
1286 let item_a = Uuid::now_v7();
1287 let item_b = Uuid::now_v7();
1288 let mut conflict = Conflict::new(
1289 ConflictType::ContradictingFact,
1290 EntityType::Artifact,
1291 item_a,
1292 EntityType::Note,
1293 item_b,
1294 );
1295 assert_eq!(conflict.status, ConflictStatus::Detected);
1296
1297 let agent_a = AgentId::now_v7();
1298 let agent_b = AgentId::now_v7();
1299 conflict = conflict.with_agents(agent_a, agent_b);
1300 assert_eq!(conflict.agent_a_id, Some(agent_a));
1301 assert_eq!(conflict.agent_b_id, Some(agent_b));
1302
1303 let trajectory_id = TrajectoryId::now_v7();
1304 conflict = conflict.with_trajectory(trajectory_id);
1305 assert_eq!(conflict.trajectory_id, Some(trajectory_id));
1306
1307 let resolution = ConflictResolutionRecord::automatic(ResolutionStrategy::Merge, "auto");
1308 conflict.resolve(resolution.clone());
1309 assert_eq!(conflict.status, ConflictStatus::Resolved);
1310 assert_eq!(conflict.resolution, Some(resolution));
1311 assert!(conflict.resolved_at.is_some());
1312 }
1313
1314 #[test]
1315 fn test_conflict_resolution_record_manual() {
1316 let resolver = AgentId::now_v7();
1317 let record = ConflictResolutionRecord::manual(ResolutionStrategy::Escalate, "ok", resolver);
1318 assert_eq!(record.resolved_by, Some(resolver));
1319 assert_eq!(record.reason, "ok");
1320 }
1321}