Skip to main content

brainwires_agent_network/identity/
agent_identity.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use uuid::Uuid;
4
5/// A protocol identifier (e.g. `"mcp"`, `"a2a"`, `"ipc"`).
6pub type ProtocolId = String;
7
8/// An agent's identity on the network.
9///
10/// Every agent that participates in the networking layer has an identity
11/// consisting of a unique ID, a human-readable name, and an [`AgentCard`]
12/// that advertises its capabilities.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct AgentIdentity {
15    /// Globally unique identifier for this agent.
16    pub id: Uuid,
17    /// Human-readable name (e.g. `"code-review-agent"`).
18    pub name: String,
19    /// Capability advertisement.
20    pub agent_card: AgentCard,
21}
22
23impl AgentIdentity {
24    /// Create a new identity with the given name and an empty agent card.
25    pub fn new(name: impl Into<String>) -> Self {
26        Self {
27            id: Uuid::new_v4(),
28            name: name.into(),
29            agent_card: AgentCard::default(),
30        }
31    }
32
33    /// Create a new identity with a specific UUID.
34    pub fn with_id(id: Uuid, name: impl Into<String>) -> Self {
35        Self {
36            id,
37            name: name.into(),
38            agent_card: AgentCard::default(),
39        }
40    }
41}
42
43/// Capability advertisement for an agent.
44///
45/// Inspired by A2A's AgentCard concept — describes what an agent can do,
46/// which protocols it speaks, and how to reach it.
47#[derive(Debug, Clone, Serialize, Deserialize, Default)]
48pub struct AgentCard {
49    /// High-level capabilities this agent offers (e.g. `"code-review"`,
50    /// `"file-editing"`, tool names).
51    pub capabilities: Vec<String>,
52    /// Protocol identifiers this agent supports (e.g. `"mcp"`, `"a2a"`,
53    /// `"ipc"`).
54    pub supported_protocols: Vec<ProtocolId>,
55    /// Arbitrary key-value metadata (e.g. model name, version, region).
56    pub metadata: HashMap<String, serde_json::Value>,
57    /// Network endpoint if the agent is directly reachable (e.g.
58    /// `"tcp://192.168.1.5:9090"` or `"unix:///tmp/agent.sock"`).
59    pub endpoint: Option<String>,
60    /// Maximum number of tasks this agent can handle concurrently.
61    pub max_concurrent_tasks: Option<usize>,
62    /// Abstract compute capacity score (higher is more powerful).
63    pub compute_capacity: Option<f64>,
64}
65
66impl AgentCard {
67    /// Check whether this agent supports a given protocol.
68    pub fn supports_protocol(&self, protocol: &str) -> bool {
69        self.supported_protocols
70            .iter()
71            .any(|p| p.eq_ignore_ascii_case(protocol))
72    }
73
74    /// Check whether this agent has a specific capability.
75    pub fn has_capability(&self, capability: &str) -> bool {
76        self.capabilities
77            .iter()
78            .any(|c| c.eq_ignore_ascii_case(capability))
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn new_identity_has_unique_id() {
88        let a = AgentIdentity::new("agent-a");
89        let b = AgentIdentity::new("agent-b");
90        assert_ne!(a.id, b.id);
91        assert_eq!(a.name, "agent-a");
92    }
93
94    #[test]
95    fn with_id_preserves_uuid() {
96        let id = Uuid::nil();
97        let identity = AgentIdentity::with_id(id, "test");
98        assert_eq!(identity.id, Uuid::nil());
99    }
100
101    #[test]
102    fn agent_card_protocol_check() {
103        let card = AgentCard {
104            supported_protocols: vec!["mcp".into(), "a2a".into()],
105            ..Default::default()
106        };
107        assert!(card.supports_protocol("MCP"));
108        assert!(card.supports_protocol("a2a"));
109        assert!(!card.supports_protocol("ipc"));
110    }
111
112    #[test]
113    fn agent_card_capability_check() {
114        let card = AgentCard {
115            capabilities: vec!["code-review".into(), "file-editing".into()],
116            ..Default::default()
117        };
118        assert!(card.has_capability("Code-Review"));
119        assert!(!card.has_capability("deploy"));
120    }
121
122    #[test]
123    fn identity_serde_roundtrip() {
124        let mut identity = AgentIdentity::new("test-agent");
125        identity.agent_card.capabilities = vec!["search".into()];
126        identity.agent_card.endpoint = Some("tcp://localhost:9090".into());
127
128        let json = serde_json::to_string(&identity).unwrap();
129        let deserialized: AgentIdentity = serde_json::from_str(&json).unwrap();
130
131        assert_eq!(deserialized.id, identity.id);
132        assert_eq!(deserialized.name, "test-agent");
133        assert_eq!(deserialized.agent_card.capabilities, vec!["search"]);
134        assert_eq!(
135            deserialized.agent_card.endpoint.as_deref(),
136            Some("tcp://localhost:9090")
137        );
138    }
139
140    #[test]
141    fn default_agent_card_is_empty() {
142        let card = AgentCard::default();
143        assert!(card.capabilities.is_empty());
144        assert!(card.supported_protocols.is_empty());
145        assert!(card.metadata.is_empty());
146        assert!(card.endpoint.is_none());
147        assert!(card.max_concurrent_tasks.is_none());
148        assert!(card.compute_capacity.is_none());
149    }
150}