1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct InvalidIdentity {
13 pub input: String,
14 pub reason: String,
15}
16
17impl fmt::Display for InvalidIdentity {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 write!(f, "invalid identity {:?}: {}", self.input, self.reason)
20 }
21}
22
23impl std::error::Error for InvalidIdentity {}
24
25fn validate_identity_string(s: &str) -> Result<(), InvalidIdentity> {
26 if s.is_empty() {
27 return Err(InvalidIdentity {
28 input: s.to_string(),
29 reason: "must not be empty".to_string(),
30 });
31 }
32 if s.contains(char::is_whitespace) {
33 return Err(InvalidIdentity {
34 input: s.to_string(),
35 reason: "must not contain whitespace".to_string(),
36 });
37 }
38 if s.contains('/') {
39 return Err(InvalidIdentity {
40 input: s.to_string(),
41 reason: "must not contain slashes".to_string(),
42 });
43 }
44 Ok(())
45}
46
47macro_rules! validated_string_newtype {
52 ($(#[$meta:meta])* $name:ident) => {
53 $(#[$meta])*
54 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55 pub struct $name(String);
56
57 impl $name {
58 pub fn parse(s: &str) -> Result<Self, InvalidIdentity> {
65 validate_identity_string(s)?;
66 Ok(Self(s.to_string()))
67 }
68
69 #[must_use]
70 pub fn as_str(&self) -> &str {
71 &self.0
72 }
73 }
74
75 impl fmt::Display for $name {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.write_str(&self.0)
78 }
79 }
80
81 impl Serialize for $name {
82 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
83 where
84 S: serde::Serializer,
85 {
86 serializer.serialize_str(&self.0)
87 }
88 }
89
90 impl<'de> Deserialize<'de> for $name {
91 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
92 where
93 D: serde::Deserializer<'de>,
94 {
95 let s = String::deserialize(deserializer)?;
96 Self::parse(&s).map_err(serde::de::Error::custom)
97 }
98 }
99 };
100}
101
102validated_string_newtype!(
103 AgentIdentity
105);
106
107validated_string_newtype!(
108 AgentRuntimeId
110);
111
112#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case")]
119pub enum AgentAddressability {
120 #[default]
121 Addressable,
122 InternalOnly,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131pub struct DisplayName(String);
132
133impl DisplayName {
134 pub fn parse(s: &str) -> Result<Self, InvalidIdentity> {
138 if s.is_empty() {
139 return Err(InvalidIdentity {
140 input: s.to_string(),
141 reason: "display name must not be empty".to_string(),
142 });
143 }
144 Ok(Self(s.to_string()))
145 }
146
147 #[must_use]
148 pub fn as_str(&self) -> &str {
149 &self.0
150 }
151}
152
153impl fmt::Display for DisplayName {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 f.write_str(&self.0)
156 }
157}
158
159impl Serialize for DisplayName {
160 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161 where
162 S: serde::Serializer,
163 {
164 serializer.serialize_str(&self.0)
165 }
166}
167
168impl<'de> Deserialize<'de> for DisplayName {
169 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170 where
171 D: serde::Deserializer<'de>,
172 {
173 let s = String::deserialize(deserializer)?;
174 Self::parse(&s).map_err(serde::de::Error::custom)
175 }
176}
177
178macro_rules! monotonic_u64_newtype {
183 ($(#[$meta:meta])* $name:ident) => {
184 $(#[$meta])*
185 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
186 #[serde(transparent)]
187 pub struct $name(u64);
188
189 impl $name {
190 #[must_use]
191 pub const fn new(value: u64) -> Self {
192 Self(value)
193 }
194
195 #[must_use]
196 pub const fn get(self) -> u64 {
197 self.0
198 }
199 }
200
201 impl fmt::Display for $name {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(f, "{}", self.0)
204 }
205 }
206 };
207}
208
209monotonic_u64_newtype!(
210 ContinuityGeneration
212);
213
214monotonic_u64_newtype!(
215 CheckpointVersion
217);
218
219monotonic_u64_newtype!(
220 FencingToken
222);
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
250pub struct CompletionCursor {
251 pub epoch: FencingToken,
253 pub turns: u64,
255}
256
257impl Default for CompletionCursor {
258 fn default() -> Self {
261 Self::start(FencingToken::new(0))
262 }
263}
264
265impl CompletionCursor {
266 #[must_use]
268 pub const fn start(epoch: FencingToken) -> Self {
269 Self { epoch, turns: 0 }
270 }
271
272 #[must_use]
273 pub const fn new(epoch: FencingToken, turns: u64) -> Self {
274 Self { epoch, turns }
275 }
276
277 #[must_use]
279 pub const fn advanced(self) -> Self {
280 Self {
281 epoch: self.epoch,
282 turns: self.turns.saturating_add(1),
283 }
284 }
285
286 #[must_use]
291 pub const fn rebased(self, epoch: FencingToken) -> Self {
292 if epoch.get() > self.epoch.get() {
293 Self::start(epoch)
294 } else {
295 self
296 }
297 }
298
299 #[must_use]
301 pub const fn progress_since(self, baseline: Self) -> CompletionProgress {
302 if self.epoch.get() != baseline.epoch.get() {
303 CompletionProgress::IncarnationChanged
304 } else if self.turns > baseline.turns {
305 CompletionProgress::Completed
306 } else {
307 CompletionProgress::Pending
308 }
309 }
310}
311
312impl fmt::Display for CompletionCursor {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 write!(f, "{}:{}", self.epoch, self.turns)
315 }
316}
317
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
321#[serde(rename_all = "snake_case")]
322pub enum CompletionProgress {
323 Pending,
325 Completed,
327 IncarnationChanged,
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339pub struct DispatchAdmission {
340 pub fencing_token: FencingToken,
341 pub durable: bool,
343 pub completion_baseline: CompletionCursor,
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
355pub struct SendAdmission {
356 pub fencing_token: FencingToken,
357 pub completion_baseline: CompletionCursor,
358}
359
360macro_rules! string_newtype {
365 ($(#[$meta:meta])* $name:ident) => {
366 $(#[$meta])*
367 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
368 #[serde(transparent)]
369 pub struct $name(String);
370
371 impl $name {
372 #[must_use]
373 pub fn new(s: impl Into<String>) -> Self {
374 Self(s.into())
375 }
376
377 #[must_use]
378 pub fn as_str(&self) -> &str {
379 &self.0
380 }
381 }
382
383 impl fmt::Display for $name {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 f.write_str(&self.0)
386 }
387 }
388 };
389}
390
391string_newtype!(
392 CorrelationId
394);
395
396string_newtype!(
397 DispatchIdempotencyKey
399);
400
401#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
407pub struct ContinuityRecord {
408 pub identity: AgentIdentity,
409 pub agent_runtime_id: AgentRuntimeId,
410 pub session_id: meerkat_core::types::SessionId,
411 pub generation: ContinuityGeneration,
412 pub checkpoint_version: CheckpointVersion,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
421#[serde(rename_all = "snake_case")]
422pub enum ContinuityFailureKind {
423 SnapshotMissing,
424 SnapshotCorrupted,
425 GenerationMismatch,
426 StoreUnavailable,
427 ResumeRejected,
431 CheckpointUnrecoverable,
439}
440
441#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
443pub struct ContinuityFailure {
444 pub identity: AgentIdentity,
445 pub kind: ContinuityFailureKind,
446 pub record: Option<ContinuityRecord>,
447 pub detail: String,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
479pub struct ContinuityUnrecoverable {
480 pub reason: String,
482}
483
484#[derive(Debug, Clone, PartialEq, Eq)]
497pub struct HostRejectedBuildPark {
498 pub reason: String,
500 pub spec_digest: u64,
502}
503
504#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
510#[serde(rename_all = "snake_case", tag = "state")]
511pub enum ContinuityResolveState {
512 Uninitialized,
513 Ready { record: ContinuityRecord },
514 Broken { failure: ContinuityFailure },
515}
516
517#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
523pub struct LeaseGrant {
524 pub identity: AgentIdentity,
525 pub fencing_token: FencingToken,
526 #[serde(
527 serialize_with = "serde_duration_ms::serialize",
528 deserialize_with = "serde_duration_ms::deserialize"
529 )]
530 pub ttl: std::time::Duration,
531}
532
533mod serde_duration_ms {
535 use serde::{Deserialize, Deserializer, Serializer};
536 use std::time::Duration;
537
538 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
539 where
540 S: Serializer,
541 {
542 let ms = duration.as_millis();
543 serializer.serialize_u64(ms as u64)
545 }
546
547 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
548 where
549 D: Deserializer<'de>,
550 {
551 let ms = u64::deserialize(deserializer)?;
552 Ok(Duration::from_millis(ms))
553 }
554}
555
556#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
562#[serde(rename_all = "snake_case", tag = "result")]
563pub enum LeaseAcquireResult {
564 Acquired(LeaseGrant),
565 AlreadyHeld {
566 identity: AgentIdentity,
567 holder: String,
568 },
569}
570
571#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
573#[serde(rename_all = "snake_case", tag = "result")]
574pub enum LeaseRenewResult {
575 Renewed(LeaseGrant),
576 Lost { identity: AgentIdentity },
577}
578
579#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
585#[serde(rename_all = "snake_case")]
586pub enum DispatchOrigin {
587 Connector,
588 Scheduler,
589 Policy,
590 Flow,
591 System,
592}
593
594#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
596pub struct DispatchInput {
597 pub content: meerkat_core::ContentInput,
598 pub origin: DispatchOrigin,
599 pub correlation_id: Option<CorrelationId>,
600 pub idempotency_key: Option<DispatchIdempotencyKey>,
601}
602
603impl DispatchInput {
604 pub fn system(text: impl Into<String>) -> Self {
606 Self {
607 content: meerkat_core::ContentInput::Text(text.into()),
608 origin: DispatchOrigin::System,
609 correlation_id: None,
610 idempotency_key: None,
611 }
612 }
613
614 pub fn with_origin(text: impl Into<String>, origin: DispatchOrigin) -> Self {
616 Self {
617 content: meerkat_core::ContentInput::Text(text.into()),
618 origin,
619 correlation_id: None,
620 idempotency_key: None,
621 }
622 }
623
624 pub fn with_correlation(mut self, id: impl Into<String>) -> Self {
626 self.correlation_id = Some(CorrelationId::new(id));
627 self
628 }
629
630 pub fn with_idempotency(mut self, key: impl Into<String>) -> Self {
632 self.idempotency_key = Some(DispatchIdempotencyKey::new(key));
633 self
634 }
635}
636
637#[derive(Debug, Clone, PartialEq, Eq)]
643pub enum ManagedPeerEdgeError {
644 SelfEdge,
645}
646
647impl fmt::Display for ManagedPeerEdgeError {
648 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649 match self {
650 Self::SelfEdge => write!(f, "self-edges are not allowed"),
651 }
652 }
653}
654
655impl std::error::Error for ManagedPeerEdgeError {}
656
657#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
662#[serde(into = "ManagedPeerEdgeRaw")]
663pub struct ManagedPeerEdge {
664 a: AgentIdentity,
665 b: AgentIdentity,
666}
667
668#[derive(Serialize, Deserialize)]
670struct ManagedPeerEdgeRaw {
671 a: AgentIdentity,
672 b: AgentIdentity,
673}
674
675impl From<ManagedPeerEdge> for ManagedPeerEdgeRaw {
676 fn from(edge: ManagedPeerEdge) -> Self {
677 Self {
678 a: edge.a,
679 b: edge.b,
680 }
681 }
682}
683
684impl TryFrom<ManagedPeerEdgeRaw> for ManagedPeerEdge {
685 type Error = ManagedPeerEdgeError;
686
687 fn try_from(raw: ManagedPeerEdgeRaw) -> Result<Self, Self::Error> {
688 Self::new(raw.a, raw.b)
689 }
690}
691
692impl<'de> Deserialize<'de> for ManagedPeerEdge {
693 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
694 where
695 D: serde::Deserializer<'de>,
696 {
697 let raw = ManagedPeerEdgeRaw::deserialize(deserializer)?;
698 Self::try_from(raw).map_err(serde::de::Error::custom)
699 }
700}
701
702impl ManagedPeerEdge {
703 pub fn new(a: AgentIdentity, b: AgentIdentity) -> Result<Self, ManagedPeerEdgeError> {
709 if a == b {
710 return Err(ManagedPeerEdgeError::SelfEdge);
711 }
712 if a < b {
713 Ok(Self { a, b })
714 } else {
715 Ok(Self { a: b, b: a })
716 }
717 }
718
719 #[must_use]
720 pub fn a(&self) -> &AgentIdentity {
721 &self.a
722 }
723
724 #[must_use]
725 pub fn b(&self) -> &AgentIdentity {
726 &self.b
727 }
728}
729
730#[derive(Debug, Clone, PartialEq, Eq)]
736pub struct NotAddressable {
737 pub identity: AgentIdentity,
738 pub addressability: AgentAddressability,
739}
740
741impl fmt::Display for NotAddressable {
742 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743 write!(
744 f,
745 "agent {:?} is not addressable (current: {:?})",
746 self.identity, self.addressability
747 )
748 }
749}
750
751impl std::error::Error for NotAddressable {}
752
753#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
759pub struct DurableAgentSpec {
760 pub identity: AgentIdentity,
761 pub profile: meerkat_mob::ProfileName,
762 #[serde(default)]
763 pub addressability: AgentAddressability,
764 pub display_name: Option<DisplayName>,
765 #[serde(default)]
766 pub labels: std::collections::BTreeMap<String, String>,
767 pub context: Option<serde_json::Value>,
768 #[serde(default)]
769 pub additional_instructions: Vec<String>,
770 #[serde(default)]
771 pub initial_message: Option<meerkat_core::ContentInput>,
772 #[serde(default)]
773 pub runtime_mode_override: Option<meerkat_mob::MobRuntimeMode>,
774 #[serde(default)]
775 pub backend: Option<meerkat_mob::MobBackendKind>,
776 #[serde(default)]
777 pub binding: Option<meerkat_contracts::WireRuntimeBinding>,
778}
779
780pub const MAX_IDENTITY_BACKGROUND_WARM_CONCURRENCY: usize = 16;
790
791#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
793#[serde(tag = "mode", rename_all = "snake_case")]
794pub enum IdentityBootstrapMode {
795 #[default]
798 EagerMaterialize,
799 LazyMaterialize,
802 LazyWithBackgroundWarm { concurrency: usize },
805}
806
807impl IdentityBootstrapMode {
808 pub fn validate(&self) -> Result<(), String> {
810 if let Self::LazyWithBackgroundWarm { concurrency } = self {
811 if *concurrency == 0 {
812 return Err("LazyWithBackgroundWarm concurrency must be greater than 0".to_string());
813 }
814 if *concurrency > MAX_IDENTITY_BACKGROUND_WARM_CONCURRENCY {
815 return Err(format!(
816 "LazyWithBackgroundWarm concurrency must be at most {MAX_IDENTITY_BACKGROUND_WARM_CONCURRENCY}"
817 ));
818 }
819 }
820 Ok(())
821 }
822
823 pub fn is_lazy(&self) -> bool {
824 !matches!(self, Self::EagerMaterialize)
825 }
826}
827
828#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
832#[serde(rename_all = "snake_case")]
833pub enum IdentityBootstrapState {
834 Dormant,
835 Warming,
836 Active,
837 Broken,
838}
839
840#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
842pub struct IdentityBootstrapEntry {
843 pub state: IdentityBootstrapState,
844 #[serde(skip_serializing_if = "Option::is_none")]
845 pub error: Option<String>,
846}
847
848#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
850pub struct IdentityBootstrapCounts {
851 pub dormant: usize,
852 pub warming: usize,
853 pub active: usize,
854 pub broken: usize,
855}
856
857#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
859pub struct IdentityBootstrapStatus {
860 pub mode: IdentityBootstrapMode,
861 pub complete: bool,
865 pub ready: bool,
867 #[serde(skip_serializing_if = "Option::is_none")]
870 pub error: Option<String>,
871 pub counts: IdentityBootstrapCounts,
872 pub identities: std::collections::BTreeMap<AgentIdentity, IdentityBootstrapEntry>,
873}
874
875impl IdentityBootstrapStatus {
876 pub fn empty(mode: IdentityBootstrapMode) -> Self {
877 Self {
878 mode,
879 complete: true,
880 ready: true,
881 error: None,
882 counts: IdentityBootstrapCounts::default(),
883 identities: std::collections::BTreeMap::new(),
884 }
885 }
886
887 pub(crate) fn refresh_aggregates(&mut self) {
888 let mut counts = IdentityBootstrapCounts::default();
889 for entry in self.identities.values() {
890 match entry.state {
891 IdentityBootstrapState::Dormant => counts.dormant += 1,
892 IdentityBootstrapState::Warming => counts.warming += 1,
893 IdentityBootstrapState::Active => counts.active += 1,
894 IdentityBootstrapState::Broken => counts.broken += 1,
895 }
896 }
897 self.ready = self.complete
898 && self.error.is_none()
899 && counts.dormant == 0
900 && counts.warming == 0
901 && counts.broken == 0;
902 self.counts = counts;
903 }
904
905 pub fn materialization_terminal(&self) -> bool {
909 self.counts.dormant == 0 && self.counts.warming == 0
910 }
911}
912
913#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
915#[serde(rename_all = "snake_case")]
916pub enum IdentityLifecycleState {
917 Dormant,
920 Active,
922 Retiring,
923 Suspended,
924 Broken,
926 Uninitialized,
927}
928
929impl IdentityLifecycleState {
930 pub fn wire_str(self) -> &'static str {
939 match self {
940 Self::Dormant => "dormant",
941 Self::Active => "active",
942 Self::Retiring => "retiring",
943 Self::Suspended => "suspended",
944 Self::Broken => "broken",
945 Self::Uninitialized => "uninitialized",
946 }
947 }
948}
949
950#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
952pub struct LeaseInfo {
953 pub fencing_token: FencingToken,
954 #[serde(
955 serialize_with = "serde_duration_ms::serialize",
956 deserialize_with = "serde_duration_ms::deserialize"
957 )]
958 pub ttl_remaining: std::time::Duration,
959 pub healthy: bool,
960}
961
962#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
964#[serde(rename_all = "snake_case", tag = "kind")]
965pub enum DurabilityPolicy {
966 SyncWriteThrough,
967 AsyncReplicated,
968 BufferedExport { max_loss_window_ms: u64 },
969}
970
971#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
973pub struct ContinuityHealth {
974 pub store_reachable: bool,
975 pub durability_policy: DurabilityPolicy,
976 pub last_checkpoint_version: Option<CheckpointVersion>,
977}
978
979#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
981pub struct IdentityStatus {
982 pub identity: AgentIdentity,
983 pub state: IdentityLifecycleState,
984 pub agent_runtime_id: Option<AgentRuntimeId>,
985 pub session_id: Option<meerkat_core::types::SessionId>,
986 pub profile: Option<meerkat_mob::ProfileName>,
987 pub runtime_mode: Option<meerkat_mob::MobRuntimeMode>,
988 pub addressability: AgentAddressability,
989 pub display_name: Option<DisplayName>,
990 #[serde(default)]
991 pub labels: std::collections::BTreeMap<String, String>,
992 pub generation: Option<ContinuityGeneration>,
993 pub checkpoint_version: Option<CheckpointVersion>,
994 pub lease: Option<LeaseInfo>,
995 pub continuity_health: Option<ContinuityHealth>,
996 #[serde(default, skip_serializing_if = "Option::is_none")]
1001 pub continuity_unrecoverable: Option<ContinuityUnrecoverable>,
1002}
1003
1004#[derive(Clone, Default)]
1009pub struct AgentRuntimeServices {
1010 mob_handle: Option<meerkat_mob::MobHandle>,
1011}
1012
1013impl AgentRuntimeServices {
1014 pub fn new(mob_handle: meerkat_mob::MobHandle) -> Self {
1015 Self {
1016 mob_handle: Some(mob_handle),
1017 }
1018 }
1019
1020 pub fn empty() -> Self {
1021 Self { mob_handle: None }
1022 }
1023
1024 pub fn mob_handle(&self) -> Option<meerkat_mob::MobHandle> {
1025 self.mob_handle.clone()
1026 }
1027
1028 pub fn has_mob_handle(&self) -> bool {
1029 self.mob_handle.is_some()
1030 }
1031}
1032
1033impl std::fmt::Debug for AgentRuntimeServices {
1034 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1035 f.debug_struct("AgentRuntimeServices")
1036 .field("mob_handle", &self.mob_handle.is_some())
1037 .finish()
1038 }
1039}
1040
1041impl PartialEq for AgentRuntimeServices {
1042 fn eq(&self, other: &Self) -> bool {
1043 self.mob_handle.is_some() == other.mob_handle.is_some()
1044 }
1045}
1046
1047impl Eq for AgentRuntimeServices {}
1048
1049#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1051pub struct AgentBuildContext {
1052 pub identity: AgentIdentity,
1053 pub active_peers: Vec<AgentIdentity>,
1054 pub managed_edges: Vec<ManagedPeerEdge>,
1055 #[serde(default, skip)]
1056 pub runtime_services: AgentRuntimeServices,
1057}
1058
1059#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1061pub struct ExternalToolDef {
1062 pub name: String,
1063 pub description: String,
1064 pub input_schema: serde_json::Value,
1065}
1066
1067#[derive(Clone, Default)]
1069pub struct LocalExternalToolOverlay {
1070 dispatcher: Option<Arc<dyn meerkat_core::agent::AgentToolDispatcher>>,
1071}
1072
1073impl LocalExternalToolOverlay {
1074 pub fn new(dispatcher: Arc<dyn meerkat_core::agent::AgentToolDispatcher>) -> Self {
1075 Self {
1076 dispatcher: Some(dispatcher),
1077 }
1078 }
1079
1080 pub fn empty() -> Self {
1081 Self { dispatcher: None }
1082 }
1083
1084 pub fn dispatcher(&self) -> Option<Arc<dyn meerkat_core::agent::AgentToolDispatcher>> {
1085 self.dispatcher.clone()
1086 }
1087
1088 pub fn is_some(&self) -> bool {
1089 self.dispatcher.is_some()
1090 }
1091}
1092
1093impl std::fmt::Debug for LocalExternalToolOverlay {
1094 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1095 f.debug_struct("LocalExternalToolOverlay")
1096 .field("dispatcher", &self.dispatcher.is_some())
1097 .finish()
1098 }
1099}
1100
1101impl PartialEq for LocalExternalToolOverlay {
1102 fn eq(&self, other: &Self) -> bool {
1103 self.dispatcher.is_some() == other.dispatcher.is_some()
1104 }
1105}
1106
1107impl Eq for LocalExternalToolOverlay {}
1108
1109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1117pub struct AgentBuildDraft {
1118 pub model: Option<String>,
1119 pub system_prompt: Option<String>,
1120 #[serde(default)]
1121 pub additional_instructions: Vec<String>,
1122 #[serde(default)]
1123 pub labels: std::collections::BTreeMap<String, String>,
1124 pub app_context: Option<serde_json::Value>,
1125 #[serde(default)]
1126 pub external_tools: Vec<ExternalToolDef>,
1127 #[serde(default, skip)]
1128 pub local_external_tools: LocalExternalToolOverlay,
1129 #[serde(default, skip_serializing_if = "Option::is_none")]
1143 pub provider_params: Option<meerkat_core::lifecycle::run_primitive::ProviderParamsOverride>,
1144}
1145
1146#[derive(Debug, Clone, PartialEq, Eq)]
1155pub struct SessionSnapshot {
1156 pub data: Vec<u8>,
1157}
1158
1159impl Serialize for SessionSnapshot {
1160 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1161 where
1162 S: serde::Serializer,
1163 {
1164 use base64::Engine;
1165 use serde::ser::SerializeStruct;
1166 let encoded = base64::engine::general_purpose::STANDARD.encode(&self.data);
1167 let mut s = serializer.serialize_struct("SessionSnapshot", 1)?;
1168 s.serialize_field("data", &encoded)?;
1169 s.end()
1170 }
1171}
1172
1173impl<'de> Deserialize<'de> for SessionSnapshot {
1174 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1175 where
1176 D: serde::Deserializer<'de>,
1177 {
1178 use base64::Engine;
1179
1180 #[derive(Deserialize)]
1181 struct Wrapper {
1182 data: String,
1183 }
1184
1185 let wrapper = Wrapper::deserialize(deserializer)?;
1186 let data = base64::engine::general_purpose::STANDARD
1187 .decode(&wrapper.data)
1188 .map_err(serde::de::Error::custom)?;
1189 Ok(Self { data })
1190 }
1191}
1192
1193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1199pub struct RosterContext {
1200 pub mob_definition: Option<meerkat_mob::MobDefinition>,
1201 pub previous_identities: Vec<AgentIdentity>,
1202}
1203
1204#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1206pub struct TopologyContext {
1207 pub roster: Vec<DurableAgentSpec>,
1208}
1209
1210#[derive(Debug)]
1216pub enum ContinuityStoreError {
1217 StaleFencingToken {
1218 identity: AgentIdentity,
1219 presented: FencingToken,
1220 current: FencingToken,
1221 },
1222 StaleCheckpointVersion {
1223 identity: AgentIdentity,
1224 presented: CheckpointVersion,
1225 current: CheckpointVersion,
1226 },
1227 StaleContinuityGeneration {
1228 identity: AgentIdentity,
1229 presented: ContinuityGeneration,
1230 current: ContinuityGeneration,
1231 },
1232 NotFound {
1233 identity: AgentIdentity,
1234 },
1235 Io(String),
1236 Corruption(String),
1237 Transient(String),
1245}
1246
1247impl fmt::Display for ContinuityStoreError {
1248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1249 match self {
1250 Self::StaleFencingToken {
1251 identity,
1252 presented,
1253 current,
1254 } => write!(
1255 f,
1256 "stale fencing token for {identity}: presented {presented}, current {current}"
1257 ),
1258 Self::StaleCheckpointVersion {
1259 identity,
1260 presented,
1261 current,
1262 } => write!(
1263 f,
1264 "stale checkpoint version for {identity}: presented {presented}, current {current}"
1265 ),
1266 Self::StaleContinuityGeneration {
1267 identity,
1268 presented,
1269 current,
1270 } => write!(
1271 f,
1272 "stale continuity generation for {identity}: presented {presented}, current {current}"
1273 ),
1274 Self::NotFound { identity } => {
1275 write!(f, "continuity record not found for {identity}")
1276 }
1277 Self::Io(msg) => write!(f, "continuity store I/O error: {msg}"),
1278 Self::Corruption(msg) => write!(f, "continuity store corruption: {msg}"),
1279 Self::Transient(msg) => {
1280 write!(f, "continuity store transient failure: {msg}")
1281 }
1282 }
1283 }
1284}
1285
1286impl std::error::Error for ContinuityStoreError {}
1287
1288#[derive(Debug)]
1290pub enum LeaseError {
1291 ProviderUnavailable(String),
1292 Io(String),
1293}
1294
1295impl fmt::Display for LeaseError {
1296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1297 match self {
1298 Self::ProviderUnavailable(msg) => {
1299 write!(f, "lease provider unavailable: {msg}")
1300 }
1301 Self::Io(msg) => write!(f, "lease I/O error: {msg}"),
1302 }
1303 }
1304}
1305
1306impl std::error::Error for LeaseError {}
1307
1308#[derive(Debug)]
1310pub enum RosterError {
1311 ProviderUnavailable(String),
1312 Io(String),
1313}
1314
1315impl fmt::Display for RosterError {
1316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1317 match self {
1318 Self::ProviderUnavailable(msg) => {
1319 write!(f, "roster provider unavailable: {msg}")
1320 }
1321 Self::Io(msg) => write!(f, "roster I/O error: {msg}"),
1322 }
1323 }
1324}
1325
1326impl std::error::Error for RosterError {}
1327
1328#[derive(Debug)]
1330pub enum TopologyError {
1331 InvalidEdge(String),
1332 ProviderUnavailable(String),
1333}
1334
1335impl fmt::Display for TopologyError {
1336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1337 match self {
1338 Self::InvalidEdge(msg) => write!(f, "invalid topology edge: {msg}"),
1339 Self::ProviderUnavailable(msg) => {
1340 write!(f, "topology provider unavailable: {msg}")
1341 }
1342 }
1343 }
1344}
1345
1346impl std::error::Error for TopologyError {}
1347
1348#[derive(Debug)]
1350pub enum CustomizerError {
1351 BuildFailed(String),
1352 Io(String),
1353}
1354
1355impl fmt::Display for CustomizerError {
1356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1357 match self {
1358 Self::BuildFailed(msg) => write!(f, "customizer build failed: {msg}"),
1359 Self::Io(msg) => write!(f, "customizer I/O error: {msg}"),
1360 }
1361 }
1362}
1363
1364impl std::error::Error for CustomizerError {}