tenzro_types/agent.rs
1//! AI Agent types for Tenzro Network
2//!
3//! This module defines types for AI agents, their capabilities,
4//! configuration, and communication on the network.
5
6use crate::primitives::{Address, Hash, Timestamp};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// AI agent identity on Tenzro Network
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct AgentIdentity {
13 /// Unique agent identifier
14 pub agent_id: String,
15 /// Agent's on-chain address
16 pub address: Address,
17 /// Agent name
18 pub name: String,
19 /// Agent version
20 pub version: String,
21 /// Agent creator/owner
22 pub creator: Address,
23}
24
25impl AgentIdentity {
26 /// Creates a new agent identity
27 pub fn new(agent_id: String, address: Address, name: String, creator: Address) -> Self {
28 Self {
29 agent_id,
30 address,
31 name,
32 version: "1.0.0".to_string(),
33 creator,
34 }
35 }
36
37 /// Sets the version
38 pub fn with_version(mut self, version: String) -> Self {
39 self.version = version;
40 self
41 }
42}
43
44/// AI agent configuration
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct AgentConfig {
47 /// Agent identity
48 pub identity: AgentIdentity,
49 /// Agent capabilities
50 pub capabilities: Vec<Capability>,
51 /// Agent description
52 pub description: String,
53 /// Execution environment requirements
54 pub execution_requirements: ExecutionRequirements,
55 /// Agent permissions
56 pub permissions: AgentPermissions,
57 /// Resource limits
58 pub resource_limits: ResourceLimits,
59 /// Configuration metadata
60 pub metadata: HashMap<String, String>,
61}
62
63impl AgentConfig {
64 /// Creates a new agent configuration
65 pub fn new(identity: AgentIdentity, capabilities: Vec<Capability>) -> Self {
66 Self {
67 identity,
68 capabilities,
69 description: String::new(),
70 execution_requirements: ExecutionRequirements::default(),
71 permissions: AgentPermissions::default(),
72 resource_limits: ResourceLimits::default(),
73 metadata: HashMap::new(),
74 }
75 }
76
77 /// Adds a description
78 pub fn with_description(mut self, description: String) -> Self {
79 self.description = description;
80 self
81 }
82
83 /// Adds metadata
84 pub fn add_metadata(&mut self, key: String, value: String) {
85 self.metadata.insert(key, value);
86 }
87}
88
89/// Capabilities that an AI agent can perform
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(tag = "type", content = "config")]
92pub enum Capability {
93 /// Natural language processing
94 NaturalLanguageProcessing {
95 /// Supported languages
96 languages: Vec<String>,
97 },
98 /// Computer vision
99 ComputerVision {
100 /// Supported vision tasks
101 tasks: Vec<String>,
102 },
103 /// Code generation and analysis
104 CodeGeneration {
105 /// Supported programming languages
106 languages: Vec<String>,
107 },
108 /// Data analysis
109 DataAnalysis {
110 /// Supported data formats
111 formats: Vec<String>,
112 },
113 /// Blockchain interaction
114 BlockchainInteraction {
115 /// Supported chains
116 chains: Vec<String>,
117 },
118 /// Smart contract execution
119 SmartContractExecution,
120 /// External API integration
121 ExternalAPIIntegration {
122 /// Supported APIs
123 apis: Vec<String>,
124 },
125 /// Multi-agent coordination
126 MultiAgentCoordination,
127 /// Custom capability
128 Custom {
129 /// Capability name
130 name: String,
131 /// Capability parameters
132 parameters: HashMap<String, String>,
133 },
134}
135
136/// Execution environment requirements for an agent
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138pub struct ExecutionRequirements {
139 /// Requires TEE execution
140 pub requires_tee: bool,
141 /// Required TEE vendor (if any)
142 pub tee_vendor: Option<String>,
143 /// Minimum memory (bytes)
144 pub min_memory: u64,
145 /// Minimum CPU cores
146 pub min_cpu_cores: u32,
147 /// Requires GPU
148 pub requires_gpu: bool,
149 /// Network access required
150 pub requires_network: bool,
151}
152
153impl Default for ExecutionRequirements {
154 fn default() -> Self {
155 Self {
156 requires_tee: false,
157 tee_vendor: None,
158 min_memory: 1024 * 1024 * 512, // 512 MB
159 min_cpu_cores: 1,
160 requires_gpu: false,
161 requires_network: true,
162 }
163 }
164}
165
166/// Agent permissions
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168pub struct AgentPermissions {
169 /// Can execute transactions
170 pub can_execute_transactions: bool,
171 /// Can access external APIs
172 pub can_access_external_apis: bool,
173 /// Can interact with other agents
174 pub can_interact_with_agents: bool,
175 /// Can store data on-chain
176 pub can_store_data: bool,
177 /// Maximum transaction value (in smallest TNZO unit)
178 pub max_transaction_value: u64,
179 /// Allowed contract addresses
180 pub allowed_contracts: Vec<Address>,
181}
182
183impl Default for AgentPermissions {
184 fn default() -> Self {
185 Self {
186 can_execute_transactions: false,
187 can_access_external_apis: true,
188 can_interact_with_agents: true,
189 can_store_data: true,
190 max_transaction_value: 0,
191 allowed_contracts: Vec::new(),
192 }
193 }
194}
195
196/// Resource limits for agent execution
197#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
198pub struct ResourceLimits {
199 /// Maximum execution time (milliseconds)
200 pub max_execution_time: u64,
201 /// Maximum memory usage (bytes)
202 pub max_memory: u64,
203 /// Maximum CPU usage percentage
204 pub max_cpu_percent: u8,
205 /// Maximum storage (bytes)
206 pub max_storage: u64,
207 /// Maximum API calls per execution
208 pub max_api_calls: u32,
209}
210
211impl Default for ResourceLimits {
212 fn default() -> Self {
213 Self {
214 max_execution_time: 60_000, // 60 seconds
215 max_memory: 1024 * 1024 * 1024, // 1 GB
216 max_cpu_percent: 80,
217 max_storage: 1024 * 1024 * 100, // 100 MB
218 max_api_calls: 100,
219 }
220 }
221}
222
223/// A message between agents on Tenzro Network
224///
225/// Wave 3d hybrid signing: signed messages carry BOTH `signature` (Ed25519
226/// classical) and `pq_signature` (ML-DSA-65). When the message is signed,
227/// both legs are `Some(_)`. When the message is unsigned (trusted
228/// single-process tests), both legs are `None`. Mixing — one `Some`, one
229/// `None` — is rejected by the router.
230#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231pub struct AgentMessage {
232 /// Message ID
233 pub message_id: String,
234 /// Sender agent
235 pub from: AgentIdentity,
236 /// Recipient agent
237 pub to: AgentIdentity,
238 /// Message type
239 pub message_type: AgentMessageType,
240 /// Message payload
241 pub payload: Vec<u8>,
242 /// Message timestamp
243 pub timestamp: Timestamp,
244 /// Classical Ed25519 message signature (None when unsigned)
245 pub signature: Option<Vec<u8>>,
246 /// Post-quantum ML-DSA-65 message signature (3309 bytes when signed,
247 /// None when unsigned). Must be present whenever `signature` is present.
248 pub pq_signature: Option<Vec<u8>>,
249 /// Reply-to message ID (if this is a reply)
250 pub reply_to: Option<String>,
251}
252
253impl AgentMessage {
254 /// Creates a new agent message
255 pub fn new(
256 from: AgentIdentity,
257 to: AgentIdentity,
258 message_type: AgentMessageType,
259 payload: Vec<u8>,
260 ) -> Self {
261 Self {
262 message_id: uuid::Uuid::new_v4().to_string(),
263 from,
264 to,
265 message_type,
266 payload,
267 timestamp: Timestamp::now(),
268 signature: None,
269 pq_signature: None,
270 reply_to: None,
271 }
272 }
273
274 /// Sets the message as a reply to another message
275 pub fn as_reply_to(mut self, message_id: String) -> Self {
276 self.reply_to = Some(message_id);
277 self
278 }
279
280 /// Adds a classical-only signature to the message.
281 ///
282 /// Wave 3d hybrid signing: this is the classical (Ed25519) leg only.
283 /// Production agent message production should call `with_hybrid_signature`
284 /// instead so both legs are populated; the router rejects messages with
285 /// only the classical leg present.
286 pub fn with_signature(mut self, signature: Vec<u8>) -> Self {
287 self.signature = Some(signature);
288 self
289 }
290
291 /// Adds a full hybrid (Ed25519 + ML-DSA-65) signature to the message.
292 pub fn with_hybrid_signature(
293 mut self,
294 classical: Vec<u8>,
295 pq: Vec<u8>,
296 ) -> Self {
297 self.signature = Some(classical);
298 self.pq_signature = Some(pq);
299 self
300 }
301
302 /// Returns the canonical bytes that should be signed.
303 ///
304 /// This deliberately **excludes** the `signature` field so that signing
305 /// followed by verifying yields a stable hash. The encoding is a simple
306 /// length-prefixed concatenation of the message-determining fields:
307 ///
308 /// ```text
309 /// signing_data = len(message_id) || message_id
310 /// || from.agent_id_len || from.agent_id || from.address
311 /// || to.agent_id_len || to.agent_id || to.address
312 /// || message_type_tag (u8)
313 /// || payload_len (u64 LE) || payload
314 /// || timestamp_millis (i64 LE)
315 /// || reply_to_present (u8) [|| reply_to_len || reply_to]
316 /// ```
317 ///
318 /// All `len()` fields are encoded as little-endian `u64` for
319 /// deterministic, length-prefixed framing. This avoids ambiguity attacks
320 /// where two different field arrangements collide into the same byte
321 /// stream (e.g. concatenating two strings without a length prefix).
322 pub fn signing_data(&self) -> Vec<u8> {
323 let mut buf = Vec::with_capacity(256 + self.payload.len());
324
325 // message_id
326 buf.extend_from_slice(&(self.message_id.len() as u64).to_le_bytes());
327 buf.extend_from_slice(self.message_id.as_bytes());
328
329 // from agent identity
330 buf.extend_from_slice(&(self.from.agent_id.len() as u64).to_le_bytes());
331 buf.extend_from_slice(self.from.agent_id.as_bytes());
332 buf.extend_from_slice(self.from.address.as_bytes());
333
334 // to agent identity
335 buf.extend_from_slice(&(self.to.agent_id.len() as u64).to_le_bytes());
336 buf.extend_from_slice(self.to.agent_id.as_bytes());
337 buf.extend_from_slice(self.to.address.as_bytes());
338
339 // message_type as a single tag byte
340 buf.push(self.message_type as u8);
341
342 // payload (length-prefixed)
343 buf.extend_from_slice(&(self.payload.len() as u64).to_le_bytes());
344 buf.extend_from_slice(&self.payload);
345
346 // timestamp
347 buf.extend_from_slice(&self.timestamp.0.to_le_bytes());
348
349 // optional reply_to (1-byte presence flag, then length-prefixed value)
350 match &self.reply_to {
351 Some(reply) => {
352 buf.push(1);
353 buf.extend_from_slice(&(reply.len() as u64).to_le_bytes());
354 buf.extend_from_slice(reply.as_bytes());
355 }
356 None => {
357 buf.push(0);
358 }
359 }
360
361 buf
362 }
363
364 /// Computes the SHA-256 hash of the message's canonical signing data.
365 ///
366 /// This is the value passed to a signer when producing the
367 /// `signature` field, and the value verified against the public key
368 /// when validating an inbound message. Because it is computed from
369 /// [`Self::signing_data`], the `signature` field itself is **not**
370 /// included in the hash, so signing is idempotent.
371 pub fn hash(&self) -> Hash {
372 use sha2::{Digest, Sha256};
373 let mut hasher = Sha256::new();
374 hasher.update(self.signing_data());
375 let digest = hasher.finalize();
376 let mut bytes = [0u8; 32];
377 bytes.copy_from_slice(&digest);
378 Hash::new(bytes)
379 }
380}
381
382/// Types of agent-to-agent messages
383#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
384pub enum AgentMessageType {
385 /// Request for task execution
386 TaskRequest,
387 /// Response to a task request
388 TaskResponse,
389 /// Query for information
390 Query,
391 /// Response to a query
392 QueryResponse,
393 /// Notification
394 Notification,
395 /// Coordination message for multi-agent tasks
396 Coordination,
397 /// Error message
398 Error,
399 /// Request to spawn a new sub-agent
400 SpawnRequest,
401 /// Confirmation that a sub-agent was successfully spawned
402 SpawnConfirmation,
403 /// Orchestrator dispatching a task to a swarm member
404 SwarmCoordination,
405 /// Swarm member returning result to orchestrator
406 SwarmResult,
407}
408
409/// A tool call made by an agent during autonomous execution
410#[derive(Debug, Clone, Serialize, Deserialize)]
411pub struct AgentToolCall {
412 /// Tool name: "spawn_agent" | "delegate_task" | "collect_results" | "complete"
413 pub tool_name: String,
414 /// Tool arguments as JSON
415 pub arguments: serde_json::Value,
416}
417
418/// Configuration for a swarm of agents
419#[derive(Debug, Clone, Serialize, Deserialize)]
420pub struct SwarmConfig {
421 /// Maximum number of member agents (default: 10)
422 pub max_members: usize,
423 /// Per-task timeout in seconds (default: 300)
424 pub task_timeout_secs: u64,
425 /// Whether to dispatch tasks in parallel (default: true)
426 pub parallel: bool,
427}
428
429impl Default for SwarmConfig {
430 fn default() -> Self {
431 Self {
432 max_members: 10,
433 task_timeout_secs: 300,
434 parallel: true,
435 }
436 }
437}
438
439/// A member of an agent swarm
440#[derive(Debug, Clone, Serialize, Deserialize)]
441pub struct SwarmMember {
442 /// Agent ID of this member
443 pub agent_id: String,
444 /// Role/name of this member in the swarm
445 pub role: String,
446 /// Current status of this member
447 pub status: SwarmMemberStatus,
448 /// Result produced by this member (if completed)
449 pub result: Option<String>,
450}
451
452/// Status of a swarm member
453#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
454pub enum SwarmMemberStatus {
455 /// Idle, waiting for a task
456 Idle,
457 /// Currently executing a task
458 Working,
459 /// Successfully completed its task
460 Completed,
461 /// Failed to complete its task
462 Failed(String),
463}