lmm_agent/types.rs
1// Copyright 2026 Mahmoud Harmouch.
2//
3// Licensed under the MIT license
4// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
5// option. This file may not be copied, modified, or distributed
6// except according to those terms.
7
8//! # Agent domain types.
9//!
10//! Foundational value types shared across all agent implementations.
11//!
12//! All types here are:
13//! - `Clone` + `Debug` + `PartialEq`
14//! - `serde::{Serialize, Deserialize}` where serialisation makes sense
15//! - `Send + Sync` - safe to move across async task boundaries
16//!
17//! ## Attribution
18//!
19//! Adapted from the `autogpt` crate's `common/utils.rs`:
20//! <https://github.com/wiseaidotdev/autogpt/blob/main/autogpt/src/common/utils.rs>
21
22use chrono::prelude::*;
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25use std::borrow::Cow;
26use std::collections::{HashMap, HashSet};
27use std::fmt;
28
29/// The current operational status of an agent through its lifecycle.
30///
31/// # Examples
32///
33/// ```
34/// use lmm_agent::types::Status;
35///
36/// let s = Status::default();
37/// assert_eq!(s, Status::Idle);
38/// ```
39#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
40pub enum Status {
41 /// Agent is waiting for a task to be assigned.
42 #[default]
43 Idle,
44 /// Agent is actively processing a task.
45 Active,
46 /// Agent is validating its own outputs.
47 InUnitTesting,
48 /// Agent has finished all assigned tasks.
49 Completed,
50 /// Agent is running the closed-loop [`ThinkLoop`] reasoning cycle.
51 Thinking,
52}
53
54impl fmt::Display for Status {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 match self {
57 Self::Idle => write!(f, "Idle"),
58 Self::Active => write!(f, "Active"),
59 Self::InUnitTesting => write!(f, "InUnitTesting"),
60 Self::Completed => write!(f, "Completed"),
61 Self::Thinking => write!(f, "Thinking"),
62 }
63 }
64}
65
66/// A single message exchanged between an agent and a user/system/tool.
67///
68/// # Examples
69///
70/// ```
71/// use lmm_agent::types::Message;
72///
73/// let msg = Message {
74/// role: "user".into(),
75/// content: "Hello, agent!".into(),
76/// };
77/// assert_eq!(msg.role.as_ref(), "user");
78/// ```
79#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
80pub struct Message {
81 /// Who produced this message (e.g. `"user"`, `"assistant"`, `"system"`).
82 pub role: Cow<'static, str>,
83 /// The message text.
84 pub content: Cow<'static, str>,
85}
86
87impl Message {
88 /// Constructs a new [`Message`] with the given role and content.
89 pub fn new(role: impl Into<Cow<'static, str>>, content: impl Into<Cow<'static, str>>) -> Self {
90 Self {
91 role: role.into(),
92 content: content.into(),
93 }
94 }
95}
96
97/// A structured knowledge base mapping fact identifiers to their explanations.
98///
99/// # Examples
100///
101/// ```
102/// use lmm_agent::types::Knowledge;
103/// use std::borrow::Cow;
104///
105/// let mut kb = Knowledge::default();
106/// kb.facts.insert(Cow::Borrowed("Rust"), Cow::Borrowed("A systems language."));
107/// assert_eq!(kb.facts.len(), 1);
108/// ```
109#[derive(Debug, PartialEq, Eq, Default, Clone, Serialize, Deserialize)]
110pub struct Knowledge {
111 /// Map from fact key to natural-language description.
112 pub facts: HashMap<Cow<'static, str>, Cow<'static, str>>,
113}
114
115impl Knowledge {
116 /// Inserts or overwrites a fact.
117 pub fn insert(
118 &mut self,
119 key: impl Into<Cow<'static, str>>,
120 value: impl Into<Cow<'static, str>>,
121 ) {
122 self.facts.insert(key.into(), value.into());
123 }
124
125 /// Looks up a fact by key.
126 pub fn get(&self, key: &str) -> Option<&Cow<'static, str>> {
127 self.facts.get(key as &str)
128 }
129}
130
131/// The name of a built-in or custom tool the agent can invoke.
132#[derive(Debug, PartialEq, Eq, Default, Clone, Hash)]
133pub enum ToolName {
134 /// Full-text web search (default).
135 #[default]
136 Search,
137 Browser,
138 News,
139 Wiki,
140 Calc,
141 Math,
142 Format,
143 Exec,
144 Code,
145 Regex,
146 Read,
147 Write,
148 Pdf,
149 Summarize,
150 Email,
151 Calendar,
152 Translate,
153 Sentiment,
154 Classify,
155 Memory,
156 Plan,
157 Spawn,
158 Judge,
159 Plugin(String),
160}
161
162/// A callable tool the agent can invoke at runtime.
163///
164/// The invocation is synchronous and deterministic - side-effectful tools
165/// (e.g. shell commands) should be wrapped in a `Plugin` variant tool with
166/// appropriate error handling inside `invoke`.
167///
168/// # Examples
169///
170/// ```
171/// use lmm_agent::types::{Tool, ToolName};
172///
173/// let echo = Tool {
174/// name: ToolName::Plugin("echo".to_string()),
175/// description: "Echoes input back.".into(),
176/// invoke: |input| input.to_string(),
177/// };
178/// assert_eq!((echo.invoke)("hello"), "hello");
179/// ```
180#[derive(Clone)]
181pub struct Tool {
182 /// The name/kind of this tool.
183 pub name: ToolName,
184 /// Human-readable description shown in planning context.
185 pub description: Cow<'static, str>,
186 /// Synchronous invocation function: `input → output`.
187 pub invoke: fn(&str) -> String,
188}
189
190impl fmt::Debug for Tool {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.debug_struct("Tool")
193 .field("name", &self.name)
194 .field("description", &self.description)
195 .finish_non_exhaustive()
196 }
197}
198
199impl PartialEq for Tool {
200 fn eq(&self, other: &Self) -> bool {
201 self.name == other.name && self.description == other.description
202 }
203}
204
205impl Default for Tool {
206 fn default() -> Self {
207 Self {
208 name: ToolName::default(),
209 description: Cow::Borrowed(""),
210 invoke: |_| String::new(),
211 }
212 }
213}
214
215/// A single goal within the agent's current plan.
216///
217/// # Examples
218///
219/// ```
220/// use lmm_agent::types::Goal;
221///
222/// let g = Goal { description: "Research Rust".into(), priority: 1, completed: false };
223/// assert!(!g.completed);
224/// ```
225#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
226pub struct Goal {
227 /// Short text describing what must be accomplished.
228 pub description: String,
229 /// Urgency level: lower values = higher urgency.
230 pub priority: u8,
231 /// Whether this goal has been achieved.
232 pub completed: bool,
233}
234
235/// An ordered sequence of [`Goal`]s the agent is working through.
236///
237/// # Examples
238///
239/// ```
240/// use lmm_agent::types::{Planner, Goal};
241///
242/// let mut p = Planner::default();
243/// p.current_plan.push(Goal { description: "Init".into(), priority: 0, completed: false });
244/// assert_eq!(p.current_plan.len(), 1);
245/// ```
246#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
247pub struct Planner {
248 /// Goals in execution order.
249 pub current_plan: Vec<Goal>,
250}
251
252impl Planner {
253 /// Returns the number of completed goals.
254 pub fn completed_count(&self) -> usize {
255 self.current_plan.iter().filter(|g| g.completed).count()
256 }
257
258 /// Returns `true` when every goal is complete.
259 pub fn is_done(&self) -> bool {
260 self.current_plan.iter().all(|g| g.completed)
261 }
262}
263
264/// The personality profile that shapes how an agent behaves and responds.
265///
266/// # Examples
267///
268/// ```
269/// use lmm_agent::types::Profile;
270///
271/// let p = Profile {
272/// name: "ResearchBot".into(),
273/// traits: vec!["curious".into(), "precise".into()],
274/// behavior_script: None,
275/// };
276/// assert_eq!(p.name.as_ref(), "ResearchBot");
277/// ```
278#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
279pub struct Profile {
280 /// Display name for this persona.
281 pub name: Cow<'static, str>,
282 /// Adjectives describing the agent's style.
283 pub traits: Vec<Cow<'static, str>>,
284 /// Optional DSL / prompt script controlling fine-grained behaviour.
285 pub behavior_script: Option<Cow<'static, str>>,
286}
287
288/// Introspection data that allows an agent to evaluate its own performance.
289pub struct Reflection {
290 /// Rolling log of recent activities or observations.
291 pub recent_logs: Vec<Cow<'static, str>>,
292 /// A function that returns a natural-language assessment of the agent.
293 pub evaluation_fn: fn(&dyn crate::traits::agent::Agent) -> Cow<'static, str>,
294}
295
296impl fmt::Debug for Reflection {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 f.debug_struct("Reflection")
299 .field("recent_logs", &self.recent_logs)
300 .finish_non_exhaustive()
301 }
302}
303
304impl PartialEq for Reflection {
305 fn eq(&self, other: &Self) -> bool {
306 self.recent_logs == other.recent_logs
307 }
308}
309
310impl Clone for Reflection {
311 fn clone(&self) -> Self {
312 Self {
313 recent_logs: self.recent_logs.clone(),
314 evaluation_fn: self.evaluation_fn,
315 }
316 }
317}
318
319impl Default for Reflection {
320 fn default() -> Self {
321 Self {
322 recent_logs: vec![],
323 evaluation_fn: default_eval_fn,
324 }
325 }
326}
327
328/// A task pinned to a specific wall-clock time.
329#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
330pub struct ScheduledTask {
331 /// UTC timestamp at which the task should run.
332 pub time: DateTime<Utc>,
333 /// The task payload.
334 pub task: Task,
335}
336
337/// Manages a queue of time-triggered tasks.
338///
339/// # Examples
340///
341/// ```
342/// use lmm_agent::types::TaskScheduler;
343///
344/// let sched = TaskScheduler::default();
345/// assert!(sched.scheduled_tasks.is_empty());
346/// ```
347#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
348pub struct TaskScheduler {
349 /// Queue of pending scheduled tasks.
350 pub scheduled_tasks: Vec<ScheduledTask>,
351}
352
353/// An atomic capability the agent possesses.
354#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
355pub enum Capability {
356 /// Can generate code from a natural-language description.
357 #[default]
358 CodeGen,
359 /// Can design user-interface layouts.
360 UIDesign,
361 /// Can execute live web searches.
362 WebSearch,
363 /// Can query SQL databases.
364 SQLAccess,
365 /// Can control robotic actuators.
366 RobotControl,
367 /// Can integrate with external REST / gRPC APIs.
368 ApiIntegration,
369 /// Can convert text to speech audio.
370 TextToSpeech,
371 /// Custom capability identified by a string label.
372 Custom(String),
373}
374
375/// Tracks recent exchanges and focuses the agent on relevant topics.
376///
377/// # Examples
378///
379/// ```
380/// use lmm_agent::types::ContextManager;
381///
382/// let ctx = ContextManager::default();
383/// assert!(ctx.recent_messages.is_empty());
384/// assert!(ctx.focus_topics.is_empty());
385/// ```
386#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
387pub struct ContextManager {
388 /// The most recent messages in the conversation window.
389 pub recent_messages: Vec<Message>,
390 /// Topics the agent is currently focused on.
391 pub focus_topics: Vec<Cow<'static, str>>,
392}
393
394/// Scope permissions for a task - controls what operations the agent may
395/// perform while executing.
396///
397/// # Examples
398///
399/// ```
400/// use lmm_agent::types::Scope;
401///
402/// let s = Scope { crud: true, auth: false, external: true };
403/// assert!(s.crud);
404/// ```
405#[derive(Debug, PartialEq, Eq, Default, Clone, Copy, Hash, Serialize, Deserialize)]
406pub struct Scope {
407 /// Allow Create / Read / Update / Delete operations.
408 pub crud: bool,
409 /// Allow authentication / authorisation related actions.
410 pub auth: bool,
411 /// Allow reaching external services or URLs.
412 pub external: bool,
413}
414
415/// Describes an HTTP route produced or consumed by the agent.
416#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
417pub struct Route {
418 /// Whether the route segment is dynamic (e.g. `"/items/{id}"`).
419 pub dynamic: Cow<'static, str>,
420 /// HTTP method (`"GET"`, `"POST"`, ...).
421 pub method: Cow<'static, str>,
422 /// Example request body as a JSON value.
423 pub body: Value,
424 /// Example response body as a JSON value.
425 pub response: Value,
426 /// Route path.
427 pub path: Cow<'static, str>,
428}
429
430/// The primary unit of work handed to an agent for execution.
431///
432/// # Examples
433///
434/// ```
435/// use lmm_agent::types::Task;
436///
437/// let task = Task::from_description("Summarise the Rust book.");
438/// assert!(!task.description.is_empty());
439/// ```
440#[derive(Debug, PartialEq, Eq, Default, Clone, Hash, Serialize, Deserialize)]
441pub struct Task {
442 /// Human-readable description of what must be accomplished.
443 pub description: Cow<'static, str>,
444 /// Optional permission scope.
445 pub scope: Option<Scope>,
446 /// External URLs the agent may need to consult.
447 pub urls: Option<Vec<Cow<'static, str>>>,
448 /// Generated or supplied frontend source code.
449 pub frontend_code: Option<Cow<'static, str>>,
450 /// Generated or supplied backend source code.
451 pub backend_code: Option<Cow<'static, str>>,
452 /// API endpoint schema discovered or generated by the agent.
453 pub api_schema: Option<Vec<Route>>,
454}
455
456// ThinkResult
457
458/// The outcome of one [`crate::agent::LmmAgent::think()`] invocation.
459///
460/// Returned by the closed-loop [`crate::cognition::r#loop::ThinkLoop`] controller.
461///
462/// # Examples
463///
464/// ```rust
465/// #[tokio::main]
466/// async fn main() {
467/// use lmm_agent::agent::LmmAgent;
468///
469/// let mut agent = LmmAgent::new("Tester".into(), "Test.".into());
470/// let result = agent.think("What is Rust ownership?").await.unwrap();
471/// assert!(result.steps > 0);
472/// }
473/// ```
474#[derive(Debug, Clone, PartialEq)]
475pub struct ThinkResult {
476 /// `true` when the Jaccard error fell below the convergence threshold.
477 pub converged: bool,
478
479 /// Number of feedback iterations executed.
480 pub steps: usize,
481
482 /// Final Jaccard distance between goal and last observation, ∈ [0, 1].
483 pub final_error: f64,
484
485 /// Snapshot of hot-store contents at termination (newest-first).
486 pub memory_snapshot: Vec<String>,
487
488 /// The per-step signals produced during the run.
489 pub signals: Vec<crate::cognition::signal::CognitionSignal>,
490}
491
492/// The active learning paradigms for the HELM engine.
493///
494/// Each variant corresponds to one sub-module of [`crate::cognition::learning`].
495/// The `Smart` variant enables all paradigms simultaneously.
496///
497/// # Examples
498///
499/// ```
500/// use lmm_agent::types::LearningMode;
501/// use std::collections::HashSet;
502///
503/// let all = LearningMode::all();
504/// assert!(all.contains(&LearningMode::QTable));
505/// assert!(all.contains(&LearningMode::Informal));
506/// ```
507#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
508pub enum LearningMode {
509 /// Tabular Bellman TD(0) Q-learning.
510 QTable,
511 /// Prototype-based task meta-adaptation.
512 MetaAdapt,
513 /// Knowledge distillation from cold store to KnowledgeIndex.
514 Distill,
515 /// Self-federated Q-table aggregation across agents.
516 Federated,
517 /// Elastic memory guard (Fisher-analog importance pinning).
518 Elastic,
519 /// Informal PMI co-occurrence mining.
520 Informal,
521 /// Enables all paradigms (alias for the full set).
522 Smart,
523}
524
525impl LearningMode {
526 /// Returns a `HashSet` containing all seven `LearningMode` variants.
527 pub fn all() -> HashSet<LearningMode> {
528 [
529 LearningMode::QTable,
530 LearningMode::MetaAdapt,
531 LearningMode::Distill,
532 LearningMode::Federated,
533 LearningMode::Elastic,
534 LearningMode::Informal,
535 LearningMode::Smart,
536 ]
537 .into_iter()
538 .collect()
539 }
540}
541
542/// A single SARS (State-Action-Reward-State') experience tuple for Q-learning.
543///
544/// # Examples
545///
546/// ```
547/// use lmm_agent::types::ExperienceRecord;
548/// use lmm_agent::cognition::learning::q_table::ActionKey;
549///
550/// let xp = ExperienceRecord { state: 1, action: ActionKey::Narrow, reward: 0.7, next_state: 2 };
551/// assert_eq!(xp.reward, 0.7);
552/// ```
553#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
554pub struct ExperienceRecord {
555 /// FNV-1a hash of the current state (query token bag).
556 pub state: u64,
557 /// The query-refinement action applied.
558 pub action: crate::cognition::learning::q_table::ActionKey,
559 /// Reward received (from `CognitionSignal::reward`).
560 pub reward: f64,
561 /// FNV-1a hash of the next state (observation token bag).
562 pub next_state: u64,
563}
564
565/// A serialisable snapshot of an agent's Q-table and reward total for federated exchange.
566///
567/// # Examples
568///
569/// ```
570/// use lmm_agent::types::AgentSnapshot;
571/// use lmm_agent::cognition::learning::q_table::QTable;
572///
573/// let snap = AgentSnapshot {
574/// agent_id: "agent-1".into(),
575/// q_table: QTable::new(0.1, 0.9, 0.0, 1.0, 0.0),
576/// total_reward: 3.5,
577/// };
578/// assert_eq!(snap.agent_id, "agent-1");
579/// ```
580#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct AgentSnapshot {
582 /// Unique identifier of the originating agent.
583 pub agent_id: String,
584 /// The agent's full Q-table at snapshot time.
585 pub q_table: crate::cognition::learning::q_table::QTable,
586 /// Total accumulated reward the agent achieved.
587 pub total_reward: f64,
588}
589
590impl Task {
591 /// Constructs a minimal [`Task`] from a plain description string.
592 pub fn from_description(description: impl Into<Cow<'static, str>>) -> Self {
593 Self {
594 description: description.into(),
595 ..Default::default()
596 }
597 }
598}
599
600// Default evaluation function
601
602/// Default introspective evaluation function.
603///
604/// Summarises goal completion progress from the agent's planner and returns a
605/// human-readable [`Cow<'static, str>`].
606///
607/// ## Attribution
608///
609/// Adapted from autogpt's `common/utils.rs`:
610/// <https://github.com/wiseaidotdev/autogpt/blob/main/autogpt/src/common/utils.rs>
611pub fn default_eval_fn(agent: &dyn crate::traits::agent::Agent) -> Cow<'static, str> {
612 if let Some(planner) = agent.planner() {
613 let total = planner.current_plan.len();
614 let completed = planner.completed_count();
615 let in_progress = total - completed;
616
617 let mut summary = format!(
618 "\n- Total Goals: {total}\n- Completed: {completed}\n- In Progress: {in_progress}\n\nGoals Summary:\n"
619 );
620
621 for goal in &planner.current_plan {
622 let state = if goal.completed { "✓" } else { "○" };
623 summary.push_str(&format!(
624 " [{state}] (priority {}) {}\n",
625 goal.priority, goal.description
626 ));
627 }
628
629 Cow::Owned(summary)
630 } else {
631 Cow::Borrowed("No planner configured.")
632 }
633}