Skip to main content

aios_protocol/
identity.rs

1//! Agent identity provider trait — the interface between the kernel contract
2//! and identity implementations (Anima or basic).
3
4use crate::ids::AgentId;
5use crate::memory::SoulProfile;
6
7/// Trait for providing agent identity to the runtime.
8///
9/// Default implementation ([`BasicIdentity`]) provides name/mission only.
10/// Anima implementation adds crypto identity, DID, policy enforcement.
11pub trait AgentIdentityProvider: Send + Sync + std::fmt::Debug {
12    /// Agent's unique identifier.
13    fn agent_id(&self) -> &AgentId;
14
15    /// Agent's soul profile (name, mission, preferences).
16    fn soul_profile(&self) -> &SoulProfile;
17
18    /// Agent's DID (did:key:z6Mk...). None for basic identity.
19    fn did(&self) -> Option<&str> {
20        None
21    }
22
23    /// Sign a JWT with the agent's identity key. None if no crypto identity.
24    fn sign_jwt(&self, _audience: &str, _ttl_secs: u64) -> Option<String> {
25        None
26    }
27
28    /// List of granted capabilities. Empty = unrestricted.
29    fn capabilities(&self) -> &[String] {
30        &[]
31    }
32
33    /// Current economic mode from belief state.
34    fn economic_mode(&self) -> &str {
35        "sovereign"
36    }
37
38    /// Check if a specific action is allowed by the agent's policy.
39    fn policy_allows(&self, _action: &str) -> bool {
40        true
41    }
42
43    /// Build a persona block for the system prompt.
44    fn persona_block(&self) -> String {
45        let soul = self.soul_profile();
46        let mut block = format!("You are {} — {}.", soul.name, soul.mission);
47        if let Some(did) = self.did() {
48            block.push_str(&format!("\nIdentity: {did}"));
49        }
50        let caps = self.capabilities();
51        if !caps.is_empty() {
52            block.push_str(&format!("\nCapabilities: {}", caps.join(", ")));
53        }
54        block.push_str(&format!("\nEconomic mode: {}", self.economic_mode()));
55        block
56    }
57}
58
59/// Basic identity provider for open-source usage (no crypto, no policy).
60#[derive(Debug, Clone, Default)]
61pub struct BasicIdentity {
62    pub agent_id: AgentId,
63    pub soul: SoulProfile,
64}
65
66impl BasicIdentity {
67    pub fn new(name: impl Into<String>, mission: impl Into<String>) -> Self {
68        Self {
69            agent_id: AgentId::default(),
70            soul: SoulProfile {
71                name: name.into(),
72                mission: mission.into(),
73                ..Default::default()
74            },
75        }
76    }
77
78    pub fn with_id(mut self, id: AgentId) -> Self {
79        self.agent_id = id;
80        self
81    }
82}
83
84impl AgentIdentityProvider for BasicIdentity {
85    fn agent_id(&self) -> &AgentId {
86        &self.agent_id
87    }
88
89    fn soul_profile(&self) -> &SoulProfile {
90        &self.soul
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn basic_identity_default() {
100        let id = BasicIdentity::default();
101        assert_eq!(id.soul_profile().name, "Agent OS agent");
102        assert_eq!(
103            id.soul_profile().mission,
104            "Run tool-mediated work safely and reproducibly"
105        );
106    }
107
108    #[test]
109    fn basic_identity_custom_name_mission() {
110        let id = BasicIdentity::new("Arcan Prime", "Runtime cognition for the Agent OS");
111        assert_eq!(id.soul_profile().name, "Arcan Prime");
112        assert_eq!(
113            id.soul_profile().mission,
114            "Runtime cognition for the Agent OS"
115        );
116    }
117
118    #[test]
119    fn basic_identity_with_id() {
120        let custom_id = AgentId::from_string("agt_custom_001");
121        let id = BasicIdentity::new("test", "test mission").with_id(custom_id.clone());
122        assert_eq!(id.agent_id(), &custom_id);
123    }
124
125    #[test]
126    fn trait_defaults_no_did() {
127        let id = BasicIdentity::default();
128        assert!(id.did().is_none());
129    }
130
131    #[test]
132    fn trait_defaults_no_jwt() {
133        let id = BasicIdentity::default();
134        assert!(id.sign_jwt("aud", 3600).is_none());
135    }
136
137    #[test]
138    fn trait_defaults_empty_capabilities() {
139        let id = BasicIdentity::default();
140        assert!(id.capabilities().is_empty());
141    }
142
143    #[test]
144    fn trait_defaults_sovereign_mode() {
145        let id = BasicIdentity::default();
146        assert_eq!(id.economic_mode(), "sovereign");
147    }
148
149    #[test]
150    fn trait_defaults_policy_allows_all() {
151        let id = BasicIdentity::default();
152        assert!(id.policy_allows("any_action"));
153        assert!(id.policy_allows("fs:write"));
154    }
155
156    #[test]
157    fn persona_block_basic() {
158        let id = BasicIdentity::new("Arcan", "Agent runtime");
159        let block = id.persona_block();
160        assert!(block.contains("You are Arcan"));
161        assert!(block.contains("Agent runtime"));
162        assert!(block.contains("Economic mode: sovereign"));
163        // No DID or capabilities for basic identity
164        assert!(!block.contains("Identity:"));
165        assert!(!block.contains("Capabilities:"));
166    }
167
168    #[test]
169    fn persona_block_with_did_and_caps() {
170        // Test with a custom implementation that provides DID and capabilities
171        #[derive(Debug)]
172        struct RichIdentity {
173            id: AgentId,
174            soul: SoulProfile,
175            did: String,
176            caps: Vec<String>,
177        }
178
179        impl AgentIdentityProvider for RichIdentity {
180            fn agent_id(&self) -> &AgentId {
181                &self.id
182            }
183            fn soul_profile(&self) -> &SoulProfile {
184                &self.soul
185            }
186            fn did(&self) -> Option<&str> {
187                Some(&self.did)
188            }
189            fn capabilities(&self) -> &[String] {
190                &self.caps
191            }
192            fn economic_mode(&self) -> &str {
193                "hustle"
194            }
195        }
196
197        let rich = RichIdentity {
198            id: AgentId::default(),
199            soul: SoulProfile {
200                name: "Agent X".into(),
201                mission: "Do things".into(),
202                ..Default::default()
203            },
204            did: "did:key:z6MkTest123".into(),
205            caps: vec!["chat:send".into(), "fs:read".into()],
206        };
207
208        let block = rich.persona_block();
209        assert!(block.contains("You are Agent X"));
210        assert!(block.contains("Identity: did:key:z6MkTest123"));
211        assert!(block.contains("Capabilities: chat:send, fs:read"));
212        assert!(block.contains("Economic mode: hustle"));
213    }
214}