Agent System Architecture
This document describes the technical architecture of Radium's agent configuration system for developers.
System Overviewβ
The agent configuration system provides a declarative way to define, discover, and manage AI agents. It consists of four main components:
- Agent Configuration (
AgentConfig) - Data structures and TOML parsing - Agent Discovery (
AgentDiscovery) - Recursive directory scanning and loading - Agent Registry (
AgentRegistry) - Thread-safe runtime management - Prompt Templates (
PromptTemplate) - Template loading and placeholder replacement
βββββββββββββββββββ
β Agent Config β TOML files in agents/ directories
β Files β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Discovery β Scans directories, loads configs
β System β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Registry β Thread-safe storage and lookup
β (Runtime) β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Prompt β Loads and renders templates
β Templates β
βββββββββββββββββββ
Component Detailsβ
AgentConfig Data Structureβ
Location: crates/radium-core/src/agents/config.rs
The AgentConfig struct represents a single agent configuration:
pub struct AgentConfig {
pub id: String, // Required: unique identifier
pub name: String, // Required: human-readable name
pub description: String, // Required: agent description
pub prompt_path: PathBuf, // Required: path to prompt template
pub mirror_path: Option<PathBuf>, // Optional: mirror path for RAD-agents
pub engine: Option<String>, // Optional: default AI engine
pub model: Option<String>, // Optional: default model
pub reasoning_effort: Option<ReasoningEffort>, // Optional: reasoning level
pub loop_behavior: Option<AgentLoopBehavior>, // Optional: loop config
pub trigger_behavior: Option<AgentTriggerBehavior>, // Optional: trigger config
pub capabilities: AgentCapabilities, // Optional: model class, cost tier, concurrency
pub sandbox: Option<SandboxConfig>, // Optional: sandbox configuration
pub category: Option<String>, // Derived from path (not in TOML)
pub file_path: Option<PathBuf>, // Set during loading (not in TOML)
}
Key Features:
- TOML serialization/deserialization via
serde - Builder pattern for programmatic construction
- Validation of required fields
- Support for optional behaviors (loop, trigger)
Related Types:
AgentConfigFile- Wrapper for TOML file operationsReasoningEffort- Enum:Low,Medium,HighAgentLoopBehavior- Loop configuration with steps, max_iterations, skipAgentTriggerBehavior- Trigger configuration with trigger_agent_id
AgentDiscovery Mechanismβ
Location: crates/radium-core/src/agents/discovery.rs
The discovery system recursively scans directories for agent configuration files.
Discovery Process:
- Search Path Resolution: Determines directories to scan (default or custom)
- Recursive Scanning: Walks directory trees looking for
.tomlfiles - Configuration Loading: Parses each TOML file into
AgentConfig - Category Derivation: Extracts category from directory structure
- Path Resolution: Resolves relative prompt paths
- Filtering: Applies sub-agent filters if configured
- Deduplication: Handles duplicate IDs (later entries override)
Default Search Paths (in precedence order):
./agents/- Project-local agents (highest precedence)~/.radium/agents/- User-level agents- Workspace agents (if applicable)
./.radium/extensions/*/agents/- Project-level extension agents~/.radium/extensions/*/agents/- User-level extension agents (lowest precedence)
API:
pub struct AgentDiscovery {
options: DiscoveryOptions,
}
impl AgentDiscovery {
pub fn new() -> Self;
pub fn with_options(options: DiscoveryOptions) -> Self;
pub fn discover_all(&self) -> Result<HashMap<String, AgentConfig>>;
pub fn find_by_id(&self, id: &str) -> Result<Option<AgentConfig>>;
pub fn list_ids(&self) -> Result<Vec<String>>;
}
Category Derivation Algorithm:
The category is derived from the file path relative to the discovery root:
agents/core/arch-agent.tomlβ category:"core"agents/custom/my-agent.tomlβ category:"custom"agents/rad-agents/design/design-agent.tomlβ category:"rad-agents/design"
AgentRegistry Implementationβ
Location: crates/radium-core/src/agents/registry.rs
The registry provides thread-safe runtime management of discovered agents.
Thread Safety:
Uses Arc<RwLock<HashMap<String, AgentConfig>>> for concurrent access:
- Multiple readers can access simultaneously (read lock)
- Writers have exclusive access (write lock)
- Lock poisoning is handled gracefully
API:
pub struct AgentRegistry {
agents: Arc<RwLock<HashMap<String, AgentConfig>>>,
}
impl AgentRegistry {
pub fn new() -> Self;
pub fn with_discovery() -> Result<Self>;
pub fn with_discovery_options(options: DiscoveryOptions) -> Result<Self>;
// Registration
pub fn register(&self, agent: AgentConfig) -> Result<()>;
pub fn register_or_replace(&self, agent: AgentConfig) -> Result<()>;
// Lookup
pub fn get(&self, id: &str) -> Result<AgentConfig>;
pub fn contains(&self, id: &str) -> Result<bool>;
// Listing
pub fn list_all(&self) -> Result<Vec<AgentConfig>>;
pub fn list_ids(&self) -> Result<Vec<String>>;
// Search and Filter
pub fn search(&self, query: &str) -> Result<Vec<AgentConfig>>;
pub fn filter<F>(&self, predicate: F) -> Result<Vec<AgentConfig>>
where F: Fn(&AgentConfig) -> bool;
// Management
pub fn count(&self) -> Result<usize>;
pub fn clear(&self) -> Result<()>;
pub fn remove(&self, id: &str) -> Result<AgentConfig>;
// Discovery Integration
pub fn discover_and_register(&self) -> Result<()>;
pub fn discover_and_register_with_options(&self, options: DiscoveryOptions) -> Result<()>;
}
Performance Characteristics:
- Lookup by ID: O(1) - HashMap lookup
- Search: O(n) - Linear scan with predicate
- Filter: O(n) - Linear scan with predicate
- Thread-safe reads: Concurrent (RwLock read)
- Thread-safe writes: Exclusive (RwLock write)
PromptTemplate Systemβ
Location: crates/radium-core/src/prompts/templates.rs
The prompt template system loads markdown templates and performs placeholder replacement.
Placeholder Syntax:
Placeholders use double braces: {{KEY}}
- Detected via character-by-character parsing
- Supports whitespace:
{{ KEY }}βKEY - Handles edge cases (nested braces, unclosed placeholders)
Rendering Modes:
- Strict Mode: Errors if placeholder is missing from context
- Non-Strict Mode: Uses default value or empty string for missing placeholders
API:
pub struct PromptTemplate {
content: String,
file_path: Option<PathBuf>,
}
impl PromptTemplate {
pub fn load(path: impl AsRef<Path>) -> Result<Self>;
pub fn from_string(content: impl Into<String>) -> Self;
pub fn render(&self, context: &PromptContext) -> Result<String>;
pub fn render_with_options(&self, context: &PromptContext, options: &RenderOptions) -> Result<String>;
pub fn list_placeholders(&self) -> Vec<String>;
}
pub struct PromptContext {
values: HashMap<String, String>,
}
impl PromptContext {
pub fn new() -> Self;
pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>);
pub fn get(&self, key: &str) -> Option<&str>;
pub fn contains(&self, key: &str) -> bool;
}
Placeholder Detection Algorithm:
- Scan content character by character
- Detect
{{opening sequence - Collect characters until
}}closing sequence - Trim whitespace from placeholder name
- Add to unique list of placeholders
- Handle edge cases (nested braces, unclosed, empty)
Component Interactionsβ
Discovery β Registry Flowβ
1. AgentDiscovery::discover_all()
ββ> Scans directories
ββ> Returns HashMap<String, AgentConfig>
2. AgentRegistry::with_discovery()
ββ> Creates AgentDiscovery
ββ> Calls discover_all()
ββ> Populates registry HashMap
Registry β Template Flowβ
1. AgentRegistry::get(id)
ββ> Returns AgentConfig
2. AgentConfig.prompt_path
ββ> Path to prompt template
3. PromptTemplate::load(path)
ββ> Loads markdown file
4. PromptTemplate::render(context)
ββ> Replaces {{KEY}} placeholders
ββ> Returns rendered string
Thread Safety Considerationsβ
AgentRegistryβ
-
Read Operations: Concurrent access via
RwLock::read()- Multiple threads can call
get(),search(),filter()simultaneously - No blocking between readers
- Multiple threads can call
-
Write Operations: Exclusive access via
RwLock::write()register(),remove(),clear()block all other operations- Writers wait for all readers to finish
-
Lock Poisoning: All methods handle
PoisonErrorgracefully- Returns
RegistryError::LockPoisonedinstead of panicking - Allows recovery from panicked threads
- Returns
AgentDiscoveryβ
- Stateless: No shared mutable state
- Thread-Safe: Can be used concurrently from multiple threads
- No Locking Required: Each discovery operation is independent
PromptTemplateβ
- Immutable: Template content doesn't change after creation
- Thread-Safe: Can be shared across threads without synchronization
- Context is Not Thread-Safe:
PromptContextshould not be shared
Extension Pointsβ
Custom Discovery Optionsβ
let mut options = DiscoveryOptions::default();
options.search_paths = vec![PathBuf::from("/custom/path")];
options.sub_agent_filter = Some(vec!["agent-1".to_string()]);
let discovery = AgentDiscovery::with_options(options);
Custom Registry Filteringβ
let registry = AgentRegistry::with_discovery()?;
// Filter by custom predicate
let filtered = registry.filter(|agent| {
agent.engine.as_ref().map(|e| e == "gemini").unwrap_or(false)
})?;
Custom Prompt Renderingβ
let template = PromptTemplate::load("prompt.md")?;
let mut context = PromptContext::new();
context.set("key", "value");
let options = RenderOptions {
strict: false,
default_value: Some("default".to_string()),
};
let rendered = template.render_with_options(&context, &options)?;
Testing Guidelinesβ
Unit Testsβ
Each component has comprehensive unit tests:
- config.rs: Tests for TOML parsing, validation, builder pattern
- discovery.rs: Tests for directory scanning, category derivation
- registry.rs: Tests for thread-safety, search, filtering
- templates.rs: Tests for placeholder detection, rendering modes
Integration Testsβ
Location: crates/radium-core/tests/agent_config_integration_test.rs
Integration tests cover:
- Full Discovery Workflow: Multiple agents, categories, path resolution
- Registry with Discovery: Lookup, search, filtering, thread-safety
- Prompt Templates: Loading, rendering, placeholder replacement
- Behaviors: Loop and trigger behavior parsing
- Error Scenarios: Missing files, invalid TOML, duplicate IDs
Test Helpers:
create_test_workspace()- Creates temporary directory structurecreate_test_agent()- Creates agent config and prompt filescreate_test_agent_full()- Creates agent with all optional fieldscreate_test_agent_with_loop_behavior()- Creates agent with loop behaviorcreate_test_agent_with_trigger_behavior()- Creates agent with trigger behavior
Running Testsβ
# Run all agent configuration tests
cargo test --test agent_config_integration_test
# Run specific test
cargo test --test agent_config_integration_test test_full_agent_discovery_workflow
# Run with output
cargo test --test agent_config_integration_test -- --nocapture
Error Handlingβ
Error Typesβ
AgentConfigError: Configuration parsing and validation errorsDiscoveryError: I/O errors, configuration errors during discoveryRegistryError: Lookup failures, lock poisoning, duplicate registrationPromptError: Template loading, missing placeholders, I/O errors
Error Propagationβ
All errors use Result<T, E> types and implement std::error::Error:
pub type Result<T> = std::result::Result<T, AgentConfigError>;
pub type Result<T> = std::result::Result<T, DiscoveryError>;
pub type Result<T> = std::result::Result<T, RegistryError>;
pub type Result<T> = std::result::Result<T, PromptError>;
Error Messagesβ
Error messages are descriptive and actionable:
"agent ID cannot be empty"- Clear validation error"agent not found: arch-agent"- Specific lookup failure"template not found: /path/to/template.md"- File not found with path"missing placeholder value: user_name"- Missing context value
Performance Considerationsβ
Discovery Performanceβ
- Directory Scanning: Uses
fs::read_dirfor recursive traversal - File I/O: Sequential file reading (could be parallelized if needed)
- Caching: No built-in caching - discovery happens on-demand
- Optimization: Consider caching discovered agents if discovery is frequent
Registry Performanceβ
- Lookup: O(1) HashMap lookup - very fast
- Search: O(n) linear scan - acceptable for typical agent counts (< 100)
- Thread Contention: Minimal with read-heavy workloads
- Memory: Stores full
AgentConfigcopies - acceptable for typical sizes
Template Performanceβ
- Placeholder Detection: O(n) single pass through content
- Replacement: O(n*m) where n is content length, m is placeholder count
- Caching: No built-in caching - templates are loaded on-demand
- Optimization: Consider caching loaded templates if same template is used frequently
Future Enhancementsβ
Potential improvements:
- Discovery Caching: Cache discovered agents to avoid repeated scanning
- Template Caching: Cache loaded templates for better performance
- Parallel Discovery: Parallelize directory scanning for large agent sets
- Hot Reloading: Watch for file changes and reload agents automatically
- Validation Rules: Extensible validation rules for agent configurations
- Agent Metadata: Additional metadata (version, author, tags)
- Agent Dependencies: Support for agent dependencies and ordering
Related Documentationβ
- User Guide: Agent Configuration - User-facing documentation
- API Documentation - Rust API docs
- CLI Reference - Command-line interface