Autonomous Agent Features
Epic 14: Autonomous Agent Features - Advanced AI capabilities for intelligent task handling and agent collaboration
Table of Contents
- Introduction
- Autonomous Planning Mode
- Auto-Generate System Prompts
- Dynamic Temperature Adjustment
- Agent Handoff Infrastructure
- Handoff Tool
- Configuration
- Best Practices
- Error Handling
- Troubleshooting
- Advanced Usage
- API Reference
Introduction
Paladin's autonomous agent features enable AI agents to intelligently handle complex tasks with minimal human intervention. These features are designed to make agents more capable, adaptive, and collaborative.
Features Overview
| Feature | Purpose | Status |
|---|---|---|
| Autonomous Planning | Decompose complex tasks into subtasks automatically | ✅ Available |
| Auto-Generate Prompts | Dynamically create system prompts based on agent role | ✅ Available |
| Dynamic Temperature | Adjust creativity based on task type | ✅ Available |
| Agent Handoffs | Delegate tasks to specialist agents | ✅ Available |
| Handoff Tool | Mid-execution agent delegation via LLM tool calls | ✅ Available |
Key Benefits
- Reduced Configuration Overhead: Less manual prompt engineering and parameter tuning
- Improved Task Handling: Automatic decomposition of complex tasks
- Adaptive Behavior: Temperature adjusts to task requirements
- Specialization: Delegate tasks to expert agents
- Opt-In Design: All features disabled by default for backward compatibility
Quick Start
#![allow(unused)] fn main() { use paladin::application::services::paladin::paladin_builder::PaladinBuilder; use paladin::core::platform::container::paladin::MaxLoops; use paladin::core::platform::container::autonomous_config::*; use std::sync::Arc; // Create autonomous configuration let autonomous_config = AutonomousConfig { planning: PlanningConfig { enabled: true, max_subtasks: 15, }, prompt_generation: PromptConfig { enabled: true, description: Some("Expert data analyst".to_string()), }, dynamic_temperature: TemperatureConfig { enabled: true, min: 0.2, max: 0.85, }, handoffs: HandoffConfig { enabled: true, strategy: HandoffStrategy::Automatic, max_depth: 5, }, }; // Build Paladin with autonomous features let paladin = PaladinBuilder::new(llm_port) .name("data-analyst") .max_loops(MaxLoops::Auto) // Autonomous planning .agent_description("Expert data analyst specializing in financial reports") .auto_generate_prompt(true) // Auto-generate system prompt .dynamic_temperature(true) // Adjust temperature dynamically .enable_handoffs() // Enable delegation .build() .await?; }
Autonomous Planning Mode
User Story US-14.1: Autonomous planning mode allows agents to decompose complex tasks into manageable subtasks automatically.
Concept
When MaxLoops::Auto is set, the Paladin uses an LLM-powered planning service to:
- Analyze the input task
- Decompose it into logical subtasks
- Execute each subtask sequentially
- Synthesize results into a final answer
This eliminates the need to manually determine iteration counts or break down complex workflows.
Use Cases
- Research Tasks: "Analyze competitor landscape and provide strategic recommendations"
- Data Analysis: "Load dataset, clean data, perform statistical analysis, and visualize results"
- Content Generation: "Research topic, create outline, write article, add citations"
- Code Development: "Design API, implement endpoints, write tests, document usage"
Configuration
#![allow(unused)] fn main() { use paladin::core::platform::container::paladin::MaxLoops; use paladin::core::platform::container::autonomous_config::PlanningConfig; // Enable autonomous planning let paladin = PaladinBuilder::new(llm_port) .name("research-agent") .max_loops(MaxLoops::Auto) // Enables planning mode .build() .await?; // Or configure via AutonomousConfig let planning_config = PlanningConfig { enabled: true, max_subtasks: 15, // Maximum subtasks to create (1-100) }; }
YAML Configuration:
autonomous:
planning:
enabled: true
max_subtasks: 15
CLI Flag:
paladin agent run --config agent.yaml --input "Research topic" --auto-plan
How It Works
1. Planning Phase
The PlanningService sends a specialized prompt to the LLM:
You are a task planner. Decompose the following complex task into
logical subtasks that can be executed sequentially.
Task: [User input]
Provide a structured plan with:
- Clear subtask descriptions
- Expected outcomes
- Dependencies between subtasks
2. Decomposition
The LLM returns a TaskPlan structure:
#![allow(unused)] fn main() { pub struct TaskPlan { pub subtasks: Vec<Subtask>, pub estimated_loops: u32, } pub struct Subtask { pub id: String, pub description: String, pub expected_outcome: String, pub dependencies: Vec<String>, } }
3. Execution
Each subtask is executed in sequence:
- Previous subtask results are included in context
- Dependencies are resolved automatically
- Loop count is set to
estimated_loops
4. Synthesis
Final loop synthesizes all subtask results into a cohesive answer.
Example Output
Input: "Analyze the performance of our web application"
Generated Plan:
- Identify Metrics: Define key performance indicators (response time, throughput, error rate)
- Collect Data: Gather performance logs and metrics from monitoring systems
- Analyze Trends: Identify patterns, bottlenecks, and anomalies in the data
- Generate Recommendations: Provide actionable suggestions for optimization
- Summarize Findings: Create executive summary with key insights
Execution: Each subtask runs sequentially, final output synthesizes all results.
Auto-Generate System Prompts
User Story US-14.2: Automatically generate contextual system prompts based on agent description.
Concept
Instead of manually writing system prompts, provide a high-level description of the agent's role and capabilities. The PromptGenerationService uses an LLM to create an optimized system prompt.
Benefits
- Consistency: All agents have well-structured prompts
- Expertise: Leverage LLM's knowledge of effective prompt patterns
- Time Savings: No manual prompt engineering required
- Adaptability: Prompts optimized for specific agent roles
Configuration
#![allow(unused)] fn main() { // Enable auto-generation in builder let paladin = PaladinBuilder::new(llm_port) .name("code-reviewer") .agent_description("Expert code reviewer specializing in Rust, security, and performance") .auto_generate_prompt(true) // Enable auto-generation .build() .await?; // Manual system prompt takes precedence let paladin_manual = PaladinBuilder::new(llm_port) .name("custom-agent") .system_prompt("Custom prompt...") // Manual override .agent_description("Description used only if prompt not set") .auto_generate_prompt(true) .build() .await?; }
YAML Configuration:
autonomous:
prompt_generation:
enabled: true
description: "Expert code reviewer specializing in Rust, security, and performance"
CLI Flag:
paladin agent run --config agent.yaml --input "Review this code" --auto-prompt
How It Works
1. Generation Request
The PromptGenerationService sends a meta-prompt:
Create an effective system prompt for an AI agent with the following role:
Agent Name: code-reviewer
Description: Expert code reviewer specializing in Rust, security, and performance
The prompt should:
- Clearly define the agent's expertise and responsibilities
- Establish appropriate tone and communication style
- Include relevant guidelines and best practices
- Be concise yet comprehensive (2-4 paragraphs)
2. LLM Response
The LLM generates a contextual system prompt:
You are an expert code reviewer with deep expertise in Rust programming,
security analysis, and performance optimization. Your role is to provide
thorough, constructive code reviews that identify issues and suggest
improvements.
When reviewing code:
1. Check for security vulnerabilities (unsafe code, input validation, etc.)
2. Analyze performance implications (algorithmic complexity, allocations)
3. Ensure idiomatic Rust patterns (ownership, borrowing, error handling)
4. Verify code clarity and maintainability
Provide specific, actionable feedback with code examples where helpful.
3. Caching
Generated prompts are cached using a deterministic hash:
#![allow(unused)] fn main() { let cache_key = format!("{}:{}", agent_name, description_hash); }
This prevents redundant LLM calls for identical agent configurations.
Regeneration
#![allow(unused)] fn main() { // Clear cache and regenerate let prompt_service = PromptGenerationService::new(llm_port); prompt_service.invalidate_cache("agent-name", "description-hash").await?; let new_prompt = prompt_service.generate_prompt("agent-name", "Updated description").await?; }
Manual Override Pattern
#![allow(unused)] fn main() { // Provide fallback but allow override let paladin = PaladinBuilder::new(llm_port) .name("analyst") .agent_description("Financial data analyst") // Used if no manual prompt .auto_generate_prompt(true) .build() .await?; // Check if prompt was generated if paladin.data().system_prompt.is_empty() { eprintln!("Warning: No system prompt generated or provided"); } }
Dynamic Temperature Adjustment
User Story US-14.3: Automatically adjust LLM temperature based on task type (factual vs. creative).
Concept
Different tasks require different levels of creativity:
- Factual tasks (calculations, data retrieval) → Low temperature (0.1-0.3)
- Analytical tasks (analysis, reasoning) → Medium temperature (0.5-0.7)
- Creative tasks (writing, brainstorming) → High temperature (0.7-0.9)
The TemperatureService classifies tasks and recommends optimal temperature.
Task Type Classification
| Task Type | Temperature Range | Examples |
|---|---|---|
| Factual | 0.1 - 0.3 | Math calculations, data lookups, API calls |
| Analytical | 0.4 - 0.6 | Code review, debugging, data analysis |
| Conversational | 0.6 - 0.7 | Chat, Q&A, general assistance |
| Creative | 0.7 - 0.9 | Writing, brainstorming, design |
Configuration
#![allow(unused)] fn main() { // Enable dynamic temperature let paladin = PaladinBuilder::new(llm_port) .name("versatile-agent") .agent_description("Multi-purpose agent for varied tasks") .dynamic_temperature(true) // Enable dynamic adjustment .temperature_bounds(0.2, 0.85) // Optional: set bounds .build() .await?; // Or via AutonomousConfig let temp_config = TemperatureConfig { enabled: true, min: 0.2, max: 0.85, }; }
YAML Configuration:
autonomous:
dynamic_temperature:
enabled: true
min: 0.2
max: 0.85
CLI Flag:
paladin agent run --config agent.yaml --input "Task" --dynamic-temp
Classification Heuristics
The TemperatureService uses keyword analysis and LLM classification:
Keyword-Based (Fast)
#![allow(unused)] fn main() { // Factual indicators if task.contains_any(&["calculate", "compute", "count", "sum"]) { return TaskType::Factual; } // Creative indicators if task.contains_any(&["write", "create", "imagine", "design"]) { return TaskType::Creative; } }
LLM-Based (Accurate)
For ambiguous tasks, the service queries the LLM:
Classify this task as: Factual, Analytical, Conversational, or Creative
Task: [User input]
Consider:
- Does it require precise, deterministic output? (Factual)
- Does it involve reasoning and analysis? (Analytical)
- Is it general conversation? (Conversational)
- Does it benefit from creative variation? (Creative)
Respond with only the classification.
How It Works
1. Task Analysis
#![allow(unused)] fn main() { let task_type = temperature_service .detect_task_type_with_llm(task_description) .await?; }
2. Temperature Calculation
#![allow(unused)] fn main() { let temperature = match task_type { TaskType::Factual => config.min.max(0.2), TaskType::Analytical => (config.min + config.max) / 2.0, TaskType::Conversational => (config.min + config.max) / 2.0 + 0.1, TaskType::Creative => config.max.min(0.85), }; }
3. Application
Temperature is applied before LLM request:
#![allow(unused)] fn main() { let request = LlmRequest { model: "gpt-4", temperature, // Dynamically calculated messages: vec![...], }; }
Example
Input: "Calculate the compound interest on $10,000 at 5% for 10 years"
- Classification: Factual
- Temperature: 0.2 (deterministic, precise)
Input: "Write a creative short story about a time traveler"
- Classification: Creative
- Temperature: 0.85 (varied, imaginative)
Agent Handoff Infrastructure
User Story US-14.4: Enable agents to delegate tasks to specialist agents.
Concept
A general-purpose agent can recognize when a task requires specialized expertise and hand it off to a specialist agent. The specialist executes the task and returns results to the original agent.
Delegation Patterns
- Automatic: Agent decides when to delegate based on context
- Explicit: Developer specifies handoff points programmatically
- Threshold-Based: Delegate when confidence drops below threshold
Configuration
#![allow(unused)] fn main() { use paladin::core::platform::container::handoff::*; // Build agent with handoff support let main_agent = PaladinBuilder::new(llm_port) .name("general-assistant") .enable_handoffs() // Enable handoff infrastructure .handoff_strategy(HandoffStrategy::Automatic) .max_handoff_depth(5) // Prevent infinite delegation chains .build() .await?; // Register specialist agents let handoff_service = HandoffService::new(llm_port); handoff_service.register_specialist( "code-expert", "Rust programming expert for code generation and debugging" ).await?; handoff_service.register_specialist( "data-analyst", "Expert in data analysis, statistics, and visualization" ).await?; }
YAML Configuration:
autonomous:
handoffs:
enabled: true
strategy: "automatic" # or "explicit" or {"threshold": 0.8}
max_depth: 5
CLI Flag:
paladin agent run --config agent.yaml --input "Task" --enable-handoffs
HandoffStrategy Options
1. Automatic
Agent decides when to delegate based on task complexity and expertise:
#![allow(unused)] fn main() { HandoffStrategy::Automatic }
2. Explicit
Developer controls handoffs programmatically:
#![allow(unused)] fn main() { HandoffStrategy::Explicit }
3. Threshold
Delegate when confidence drops below threshold:
#![allow(unused)] fn main() { HandoffStrategy::threshold(0.75) // Delegate if confidence < 75% }
Circular Handoff Prevention
The HandoffService prevents infinite delegation loops:
#![allow(unused)] fn main() { // Validation in should_handoff() if handoff_chain.contains(&target_agent) { return Err(HandoffError::CircularHandoff { chain: handoff_chain.clone(), attempted_target: target_agent.to_string(), }); } }
Example:
- Agent A → Agent B → Agent C ✅ Valid
- Agent A → Agent B → Agent A ❌ Circular (rejected)
Max Depth Configuration
Prevent unbounded delegation chains:
#![allow(unused)] fn main() { let handoff_config = HandoffConfig { enabled: true, strategy: HandoffStrategy::Automatic, max_depth: 5, // Maximum 5 hops }; }
Example:
- A → B → C → D → E ✅ Depth 5 (allowed)
- A → B → C → D → E → F ❌ Depth 6 (rejected)
Context Transfer
When handing off, context is preserved and transferred:
#![allow(unused)] fn main() { pub struct HandoffContext { pub original_task: String, pub accumulated_results: Vec<String>, pub handoff_chain: Vec<String>, pub depth: u32, pub metadata: HashMap<String, String>, } }
The specialist receives:
- Original task description
- All previous agent outputs
- Current position in handoff chain
- Any custom metadata
Decision Process
#![allow(unused)] fn main() { // HandoffService determines if handoff is needed let decision = handoff_service .should_handoff(task, current_agent, context) .await?; match decision { HandoffDecision::Complete => { // Task can be completed by current agent } HandoffDecision::Handoff { target_agent, reason } => { // Delegate to specialist let result = execute_handoff(target_agent, task).await?; } } }
Handoff Tool
User Story US-14.5: Mid-execution agent delegation via LLM tool calls.
Concept
The handoff_to_agent tool is automatically registered with agents that have handoffs enabled. During execution, the LLM can invoke this tool to delegate tasks to specialists.
Tool Schema
{
"name": "handoff_to_agent",
"description": "Delegate the current task to a specialist agent when their expertise is needed",
"parameters": {
"type": "object",
"properties": {
"agent_name": {
"type": "string",
"enum": ["code-expert", "data-analyst", "security-specialist"],
"description": "Name of the specialist agent to hand off to"
},
"message": {
"type": "string",
"description": "Clear task description for the specialist agent"
}
},
"required": ["agent_name", "message"]
}
}
Note: The enum values for agent_name are dynamically populated based on registered specialists.
Example LLM Tool Call
When the LLM recognizes specialized expertise is needed:
{
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "handoff_to_agent",
"arguments": "{\"agent_name\": \"code-expert\", \"message\": \"Review this Rust function for memory safety issues: fn process_data(data: Vec<u8>) { ... }\"}"
}
}
]
}
Auto-Registration
The HandoffTool is automatically registered when handoffs are enabled:
#![allow(unused)] fn main() { // Automatic registration in PaladinBuilder if self.handoffs_enabled { let handoff_tool = HandoffTool::new( self.specialist_list.clone(), self.handoff_service.clone() ); arsenal.register_tool(Arc::new(handoff_tool)).await?; } }
No manual tool registration required!
Error Scenarios
1. Invalid Agent
{"agent_name": "nonexistent-agent", "message": "..."}
Error: HandoffError::InvalidAgent
Error: Agent 'nonexistent-agent' is not registered as a specialist.
Available agents: code-expert, data-analyst, security-specialist
2. Circular Handoff
general-agent → code-expert → general-agent (attempt)
Error: HandoffError::CircularHandoff
Error: Circular handoff detected.
Chain: general-agent → code-expert → general-agent
Cannot hand back to an agent already in the chain.
3. Max Depth Exceeded
A → B → C → D → E → F (max_depth = 5)
Error: HandoffError::MaxDepthExceeded
Error: Maximum handoff depth (5) exceeded.
Current chain: A → B → C → D → E → F
Execution Flow
- LLM invokes tool:
handoff_to_agent(agent_name="code-expert", message="...") - Validation: Check agent exists, no circular handoff, depth OK
- Context preparation: Build HandoffContext with chain history
- Specialist execution: Target agent receives task and context
- Result return: Specialist output returned to original agent
- Continuation: Original agent incorporates result and continues
Configuration
Autonomous features can be configured via YAML files, CLI flags, or the Builder API.
YAML Configuration
Complete example (config.yml):
autonomous:
# Autonomous Planning (US-14.1)
planning:
enabled: true
max_subtasks: 15
# Auto-Generate System Prompt (US-14.2)
prompt_generation:
enabled: true
description: "Expert data analyst specializing in financial reports"
# Dynamic Temperature Adjustment (US-14.3)
dynamic_temperature:
enabled: true
min: 0.2
max: 0.85
# Agent Handoff (US-14.4 & US-14.5)
handoffs:
enabled: true
strategy: "automatic" # Options: "automatic", "explicit", {"threshold": 0.8}
max_depth: 5
CLI Flags
# Enable all autonomous features
paladin agent run \
--config agent.yaml \
--input "Complex task" \
--auto-plan \
--auto-prompt \
--dynamic-temp \
--enable-handoffs
# Enable specific features
paladin agent run \
--config agent.yaml \
--input "Calculate compound interest" \
--dynamic-temp # Only dynamic temperature
Builder API
#![allow(unused)] fn main() { use paladin::application::services::paladin::paladin_builder::PaladinBuilder; use paladin::core::platform::container::paladin::MaxLoops; use paladin::core::platform::container::autonomous_config::*; let autonomous_config = AutonomousConfig { planning: PlanningConfig { enabled: true, max_subtasks: 15, }, prompt_generation: PromptConfig { enabled: true, description: Some("Financial analyst".to_string()), }, dynamic_temperature: TemperatureConfig { enabled: true, min: 0.2, max: 0.85, }, handoffs: HandoffConfig { enabled: true, strategy: HandoffStrategy::Automatic, max_depth: 5, }, }; let paladin = PaladinBuilder::new(llm_port) .name("analyst") // Method 1: Individual feature methods .max_loops(MaxLoops::Auto) .agent_description("Financial analyst") .auto_generate_prompt(true) .dynamic_temperature(true) .temperature_bounds(0.2, 0.85) .enable_handoffs() .handoff_strategy(HandoffStrategy::Automatic) .max_handoff_depth(5) // Method 2: Configuration object // .with_autonomous_config(autonomous_config) .build() .await?; }
Configuration Precedence
When multiple configuration sources are present:
Precedence Order (highest to lowest):
- CLI Flags:
--auto-plan,--auto-prompt, etc. - Builder API: Explicit method calls
- YAML Configuration:
config.ymlfile - Defaults: All features disabled
Example:
#![allow(unused)] fn main() { // YAML says planning disabled // Builder says planning enabled let paladin = PaladinBuilder::new(llm_port) .load_config_from_yaml("config.yml") // planning: enabled: false .max_loops(MaxLoops::Auto) // Builder overrides YAML .build().await?; // Result: Planning is ENABLED (builder takes precedence) }
Environment Variables
Override configuration via environment variables:
# Planning
export APP_AUTONOMOUS_PLANNING_ENABLED=true
export APP_AUTONOMOUS_PLANNING_MAX_SUBTASKS=20
# Prompt Generation
export APP_AUTONOMOUS_PROMPT_GENERATION_ENABLED=true
export APP_AUTONOMOUS_PROMPT_GENERATION_DESCRIPTION="Expert coder"
# Dynamic Temperature
export APP_AUTONOMOUS_DYNAMIC_TEMPERATURE_ENABLED=true
export APP_AUTONOMOUS_DYNAMIC_TEMPERATURE_MIN=0.2
export APP_AUTONOMOUS_DYNAMIC_TEMPERATURE_MAX=0.8
# Handoffs
export APP_AUTONOMOUS_HANDOFFS_ENABLED=true
export APP_AUTONOMOUS_HANDOFFS_STRATEGY=explicit
export APP_AUTONOMOUS_HANDOFFS_MAX_DEPTH=10
Best Practices
When to Use Each Feature
Autonomous Planning
✅ Use when:
- Task is complex and multi-step
- Breaking down manually is time-consuming
- Workflow is exploratory (research, analysis)
❌ Avoid when:
- Task is simple and single-step
- Exact workflow is known and fixed
- Real-time performance is critical (adds planning overhead)
Auto-Generate Prompts
✅ Use when:
- Creating many agents with similar roles
- Standardizing prompt quality across agents
- Experimenting with new agent configurations
❌ Avoid when:
- Highly specialized prompts requiring domain expertise
- Production agents where prompt is carefully tuned
- Prompt generation costs are a concern
Dynamic Temperature
✅ Use when:
- Agent handles diverse task types
- Task type varies per execution
- Optimal temperature is unknown
❌ Avoid when:
- Agent has single, consistent task type
- Temperature is already well-tuned
- Task classification overhead is unacceptable
Agent Handoffs
✅ Use when:
- Multiple specialized agents exist
- Tasks require varied expertise
- Collaboration improves outcomes
❌ Avoid when:
- Single agent can handle all tasks
- Handoff overhead exceeds benefits
- Linear workflow is more efficient
Performance Considerations
Token Usage
- Planning: Adds ~500-1500 tokens for plan generation
- Prompt Generation: One-time cost of ~300-800 tokens (cached)
- Temperature Classification: ~200-400 tokens per classification
- Handoffs: ~200 tokens per handoff decision + specialist execution
Optimization:
#![allow(unused)] fn main() { // Use planning selectively let use_planning = task_length > 100 || task_complexity > 0.7; let max_loops = if use_planning { MaxLoops::Auto } else { MaxLoops::Fixed(3) }; }
Latency
- Planning: +1-3 seconds for plan generation
- Prompt Generation: +0.5-2 seconds (only on first execution)
- Temperature Classification: +0.3-1 second per task
- Handoffs: +LLM latency per hop (2-5 seconds typical)
Optimization:
#![allow(unused)] fn main() { // Disable features for latency-sensitive tasks if real_time_required { builder.dynamic_temperature(false); builder.max_loops(MaxLoops::Fixed(1)); } }
Cost Management
- Estimate costs: Calculate token usage for budget planning
- Cache prompts: Prompt generation is cached automatically
- Limit depth: Set reasonable
max_handoff_depth(3-5) - Monitor usage: Track autonomous feature LLM calls
Token Budget Management
#![allow(unused)] fn main() { // Calculate estimated token usage let base_tokens = 1000; // Task input + output let planning_tokens = planning_enabled ? 1000 : 0; let prompt_gen_tokens = prompt_gen_enabled && !cached ? 500 : 0; let temp_tokens = dynamic_temp_enabled ? 300 : 0; let handoff_tokens = handoffs_enabled ? 200 * max_depth : 0; let estimated_total = base_tokens + planning_tokens + prompt_gen_tokens + temp_tokens + handoff_tokens; if estimated_total > budget { // Disable or reduce features } }
Combining Features Effectively
Recommended Combinations
Research Agent (Exploration & Analysis):
#![allow(unused)] fn main() { .max_loops(MaxLoops::Auto) // Plan research steps .dynamic_temperature(true) // Adapt to analysis vs. writing .enable_handoffs() // Delegate to specialists }
Code Generation Agent (Precision & Expertise):
#![allow(unused)] fn main() { .auto_generate_prompt(true) // Standard prompt template .dynamic_temperature(true) // Low temp for code, high for docs .enable_handoffs() // Delegate to security expert }
Customer Support Agent (Conversational & Adaptive):
#![allow(unused)] fn main() { .dynamic_temperature(true) // Conversational tone .enable_handoffs() // Escalate to specialists }
Data Analysis Agent (Structured & Methodical):
#![allow(unused)] fn main() { .max_loops(MaxLoops::Auto) // Break down analysis steps .auto_generate_prompt(true) // Role-based prompt .dynamic_temperature(true) // Analytical temperature }
Avoid Over-Configuration
❌ Too much:
#![allow(unused)] fn main() { .max_loops(MaxLoops::Auto) // Planning .auto_generate_prompt(true) // Auto prompt .dynamic_temperature(true) // Dynamic temp .enable_handoffs() // Handoffs .max_handoff_depth(10) // Deep chains // Result: Slow, expensive, complex debugging }
✅ Balanced:
#![allow(unused)] fn main() { .max_loops(MaxLoops::Fixed(5)) // Fixed loops .system_prompt("...") // Manual prompt (tuned) .dynamic_temperature(true) // Only dynamic temp // Result: Fast, cost-effective, predictable }
Error Handling
Autonomous features have specific error types for different failure modes.
PlanningError
#![allow(unused)] fn main() { pub enum PlanningError { /// LLM failed to generate a valid plan PlanGenerationFailed(String), /// Generated plan has no subtasks EmptyPlan, /// Subtask dependencies are circular CircularDependencies(Vec<String>), /// LLM provider error during planning LlmError(LlmError), } }
Handling:
#![allow(unused)] fn main() { use paladin::core::platform::container::planning::PlanningError; match paladin.execute(input).await { Err(PaladinError::Planning(PlanningError::PlanGenerationFailed(msg))) => { eprintln!("Failed to generate plan: {}", msg); // Fallback: Use fixed loop count paladin.config_mut().max_loops = MaxLoops::Fixed(5); paladin.execute(input).await? } Err(PaladinError::Planning(PlanningError::EmptyPlan)) => { eprintln!("LLM returned empty plan, using default execution"); // Fallback: Single execution paladin.config_mut().max_loops = MaxLoops::Fixed(1); paladin.execute(input).await? } Ok(result) => result, Err(e) => return Err(e), } }
PromptError
#![allow(unused)] fn main() { pub enum PromptError { /// LLM failed to generate a valid prompt GenerationFailed(String), /// Agent description is missing or empty MissingDescription, /// Generated prompt is too short/long InvalidLength { length: usize, min: usize, max: usize }, /// LLM provider error during generation LlmError(LlmError), } }
Handling:
#![allow(unused)] fn main() { use paladin::core::platform::container::prompt::PromptError; let builder = PaladinBuilder::new(llm_port) .agent_description("Analyst") .auto_generate_prompt(true); match builder.build().await { Err(PaladinError::Prompt(PromptError::MissingDescription)) => { eprintln!("Agent description required for auto-prompt"); // Fallback: Use default prompt PaladinBuilder::new(llm_port) .system_prompt("You are a helpful AI assistant.") .build().await? } Err(PaladinError::Prompt(PromptError::GenerationFailed(msg))) => { eprintln!("Prompt generation failed: {}", msg); // Fallback: Manual prompt PaladinBuilder::new(llm_port) .system_prompt("You are an analyst.") .build().await? } Ok(paladin) => paladin, Err(e) => return Err(e), } }
HandoffError
#![allow(unused)] fn main() { pub enum HandoffError { /// Target agent not found in registry InvalidAgent(String), /// Circular handoff detected CircularHandoff { chain: Vec<String>, attempted_target: String }, /// Maximum handoff depth exceeded MaxDepthExceeded { current_depth: u32, max_depth: u32 }, /// Specialist execution failed ExecutionFailed { agent: String, error: String }, /// LLM provider error during handoff LlmError(LlmError), } }
Handling:
#![allow(unused)] fn main() { use paladin::core::platform::container::handoff::{HandoffError, HandoffDecision}; match handoff_service.should_handoff(task, agent, context).await { Ok(HandoffDecision::Handoff { target_agent, .. }) => { match execute_handoff(&target_agent, task).await { Ok(result) => result, Err(HandoffError::InvalidAgent(name)) => { eprintln!("Agent '{}' not found, continuing with current agent", name); // Fallback: Current agent completes task current_agent.execute(task).await? } Err(HandoffError::CircularHandoff { chain, attempted_target }) => { eprintln!("Circular handoff detected: {:?} -> {}", chain, attempted_target); // Fallback: Break chain, current agent completes current_agent.execute(task).await? } Err(HandoffError::MaxDepthExceeded { current_depth, max_depth }) => { eprintln!("Max depth {} exceeded (current: {})", max_depth, current_depth); // Fallback: No more handoffs, finish with current agent current_agent.execute(task).await? } Err(e) => return Err(e), } } Ok(HandoffDecision::Complete) => { // No handoff needed current_agent.execute(task).await? } Err(e) => return Err(e.into()), } }
Graceful Degradation
Pattern: Disable feature on error, continue execution
#![allow(unused)] fn main() { async fn execute_with_fallback(paladin: &Paladin, input: &str) -> Result<String> { // Try with autonomous features match paladin.execute(input).await { Ok(result) => Ok(result.output), // Planning failed: retry with fixed loops Err(PaladinError::Planning(_)) => { eprintln!("Planning failed, using fixed execution"); let mut config = paladin.config().clone(); config.max_loops = MaxLoops::Fixed(3); paladin.execute_with_config(input, config).await .map(|r| r.output) } // Handoff failed: continue without delegation Err(PaladinError::Handoff(_)) => { eprintln!("Handoff failed, completing task without delegation"); let mut config = paladin.config().clone(); config.handoffs_enabled = false; paladin.execute_with_config(input, config).await .map(|r| r.output) } // Other errors: propagate Err(e) => Err(e), } } }
Troubleshooting
Common Issues and Solutions
Issue: Planning generates too many subtasks
Symptom: Plans have 20+ subtasks, execution is slow
Solution:
autonomous:
planning:
max_subtasks: 10 # Reduce limit
Or provide more focused input:
#![allow(unused)] fn main() { // ❌ Too broad "Analyze the company's performance" // ✅ More focused "Analyze Q4 2025 revenue trends and identify top 3 growth drivers" }
Issue: Generated prompts are too generic
Symptom: Auto-generated prompts lack specificity
Solution: Provide detailed agent descriptions
#![allow(unused)] fn main() { // ❌ Too vague .agent_description("Analyst") // ✅ Specific .agent_description( "Senior financial analyst specializing in SaaS companies, \ with expertise in revenue forecasting, churn analysis, and \ unit economics. Focus on actionable insights and data-driven \ recommendations." ) }
Issue: Wrong temperature for task
Symptom: Factual tasks get creative outputs, or vice versa
Solution: Check classification logic or override manually
#![allow(unused)] fn main() { // Option 1: Provide clearer task description // ❌ Ambiguous "Tell me about quantum computing" // ✅ Clear intent "Calculate the energy levels of a hydrogen atom" // → Factual // Option 2: Manual override .temperature(0.2) // Force low temperature .dynamic_temperature(false) // Disable auto-adjustment }
Issue: Circular handoff errors
Symptom: HandoffError::CircularHandoff errors
Solution: Review agent configurations and handoff logic
#![allow(unused)] fn main() { // Check specialist capabilities don't overlap handoff_service.register_specialist( "code-expert", "Rust code generation and debugging (does NOT do security audits)" ); handoff_service.register_specialist( "security-expert", "Security audits and vulnerability analysis (does NOT write code)" ); }
Issue: Max depth exceeded
Symptom: HandoffError::MaxDepthExceeded errors
Solution: Increase max_depth or simplify task delegation
autonomous:
handoffs:
max_depth: 10 # Increase limit
Or break complex delegation into separate Paladin executions.
Issue: Features not activating
Symptom: Autonomous features appear disabled despite configuration
Solution: Verify configuration precedence
#![allow(unused)] fn main() { // Check 1: Configuration loaded? println!("Config: {:?}", paladin.config()); // Check 2: Builder methods called? let paladin = PaladinBuilder::new(llm_port) .auto_generate_prompt(true) // Must be true .agent_description("...") // Must be provided .build().await?; // Check 3: CLI flags passed? // paladin agent run --auto-prompt (must include flag) }
Debugging Tips
Enable Logging
export RUST_LOG=paladin=debug,paladin::application::services::paladin=trace
# Run with verbose output
paladin agent run --config agent.yaml --input "Task" --verbose
Output:
DEBUG paladin::planning: Generating plan for task: "Analyze data"
DEBUG paladin::planning: Plan generated with 5 subtasks
TRACE paladin::planning: Subtask 1: Load dataset
TRACE paladin::planning: Subtask 2: Clean data
...
Tracing
Use OpenTelemetry for distributed tracing:
#![allow(unused)] fn main() { use tracing::{info, debug, span, Level}; let span = span!(Level::INFO, "autonomous_execution"); let _enter = span.enter(); info!("Starting execution with planning enabled"); debug!(max_subtasks = config.planning.max_subtasks, "Planning configuration"); // Execution... }
Inspect Intermediate Results
#![allow(unused)] fn main() { // Enable detailed output let result = paladin.execute(input).await?; println!("Execution time: {}ms", result.execution_time_ms); println!("Loops completed: {}", result.loop_count); println!("Stop reason: {:?}", result.stop_reason); // Access plan (if available) if let Some(plan) = result.plan { println!("Generated plan:"); for subtask in plan.subtasks { println!(" - {}: {}", subtask.id, subtask.description); } } // Access handoff history for handoff in result.handoff_history { println!("Handoff: {} -> {} ({})", handoff.from_agent, handoff.to_agent, handoff.reason); } }
Performance Optimization Tips
Reduce Token Usage
#![allow(unused)] fn main() { // Disable expensive features for simple tasks if task.len() < 50 { builder .max_loops(MaxLoops::Fixed(1)) .dynamic_temperature(false); } }
Cache Aggressively
#![allow(unused)] fn main() { // Prompt generation caches automatically // For other expensive operations, implement caching: use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; let task_type_cache: Arc<RwLock<HashMap<String, TaskType>>> = Arc::new(RwLock::new(HashMap::new())); // Check cache before classification if let Some(cached_type) = task_type_cache.read().await.get(task) { return Ok(*cached_type); } }
Parallel Execution
#![allow(unused)] fn main() { // For independent tasks, use Phalanx (parallel execution) let phalanx = Phalanx::new(vec![paladin1, paladin2, paladin3]); let results = phalanx.execute(inputs).await?; }
Optimize Handoff Strategy
#![allow(unused)] fn main() { // Use explicit handoffs for predictable workflows builder.handoff_strategy(HandoffStrategy::Explicit); // Implement custom decision logic if task_requires_specialist(&task) { execute_handoff("specialist", &task).await? } else { current_agent.execute(&task).await? } }
Advanced Usage
Combining Autonomous Features
Example: Research & Analysis Agent
#![allow(unused)] fn main() { let research_agent = PaladinBuilder::new(llm_port) .name("research-analyst") .max_loops(MaxLoops::Auto) // Plan research steps .agent_description( "Expert research analyst with skills in literature review, \ data synthesis, and academic writing" ) .auto_generate_prompt(true) // Generate researcher prompt .dynamic_temperature(true) // Analytical + creative .temperature_bounds(0.3, 0.8) .enable_handoffs() // Delegate to specialists .handoff_strategy(HandoffStrategy::threshold(0.7)) .build() .await?; // Register specialists handoff_service.register_specialist( "statistics-expert", "Statistical analysis and data interpretation" ).await?; handoff_service.register_specialist( "writer", "Academic and technical writing" ).await?; // Execute complex research task let result = research_agent .execute("Research the impact of AI on software development productivity") .await?; }
Example: Code Generation Agent
#![allow(unused)] fn main() { let code_agent = PaladinBuilder::new(llm_port) .name("code-generator") .agent_description( "Expert Rust developer specializing in safe, idiomatic code \ with comprehensive error handling and documentation" ) .auto_generate_prompt(true) // Generate coder prompt .dynamic_temperature(true) // Low for code, higher for docs .temperature_bounds(0.1, 0.6) .enable_handoffs() // Delegate testing & review .build() .await?; // Register specialists handoff_service.register_specialist( "test-engineer", "Unit and integration test generation" ).await?; handoff_service.register_specialist( "security-auditor", "Security review and vulnerability scanning" ).await?; // Generate with automatic testing and review let result = code_agent .execute("Create a secure REST API endpoint for user authentication") .await?; }
Custom Agent Configurations
Multi-Stage Pipeline
#![allow(unused)] fn main() { // Stage 1: Planning let planner = PaladinBuilder::new(llm_port) .name("planner") .max_loops(MaxLoops::Auto) .auto_generate_prompt(true) .agent_description("Task decomposition specialist") .build() .await?; // Stage 2: Execution let executor = PaladinBuilder::new(llm_port) .name("executor") .max_loops(MaxLoops::Fixed(1)) .dynamic_temperature(true) .enable_handoffs() .build() .await?; // Stage 3: Review let reviewer = PaladinBuilder::new(llm_port) .name("reviewer") .max_loops(MaxLoops::Fixed(1)) .temperature(0.3) // Analytical .system_prompt("Review the output for completeness and accuracy") .build() .await?; // Execute pipeline let plan_result = planner.execute(task).await?; let exec_result = executor.execute(&plan_result.output).await?; let final_result = reviewer.execute(&exec_result.output).await?; }
Adaptive Agent
#![allow(unused)] fn main() { // Agent that adjusts its configuration based on feedback struct AdaptiveAgent { paladin: Paladin, performance_history: Vec<f32>, } impl AdaptiveAgent { async fn execute_adaptive(&mut self, task: &str) -> Result<String> { // Adjust based on historical performance let avg_performance = self.performance_history.iter().sum::<f32>() / self.performance_history.len() as f32; if avg_performance < 0.7 { // Performance is low, enable more features self.paladin.config_mut().max_loops = MaxLoops::Auto; self.paladin.config_mut().enable_handoffs = true; } else { // Performance is good, optimize for speed self.paladin.config_mut().max_loops = MaxLoops::Fixed(3); self.paladin.config_mut().enable_handoffs = false; } let result = self.paladin.execute(task).await?; // Record performance let performance = self.calculate_performance(&result); self.performance_history.push(performance); Ok(result.output) } } }
Integration with Battalion Patterns
Formation with Autonomous Agents
#![allow(unused)] fn main() { use paladin::application::services::battalion::formation_service::FormationService; // Create autonomous agents let agent1 = PaladinBuilder::new(llm_port.clone()) .name("researcher") .max_loops(MaxLoops::Auto) .auto_generate_prompt(true) .agent_description("Research and data gathering specialist") .build().await?; let agent2 = PaladinBuilder::new(llm_port.clone()) .name("analyst") .dynamic_temperature(true) .auto_generate_prompt(true) .agent_description("Data analysis and insights expert") .build().await?; let agent3 = PaladinBuilder::new(llm_port.clone()) .name("writer") .temperature(0.7) .auto_generate_prompt(true) .agent_description("Report writing and documentation specialist") .build().await?; // Formation: Sequential execution (output N → input N+1) let formation = FormationService::new(); let result = formation.execute( vec![agent1, agent2, agent3], "Analyze market trends in AI industry" ).await?; }
Phalanx with Handoffs
#![allow(unused)] fn main() { use paladin::application::services::battalion::phalanx_service::PhalanxService; // Create agents with handoff capabilities let agents: Vec<Paladin> = vec![ PaladinBuilder::new(llm_port.clone()) .name("competitor-analyzer") .enable_handoffs() .build().await?, PaladinBuilder::new(llm_port.clone()) .name("market-researcher") .enable_handoffs() .build().await?, PaladinBuilder::new(llm_port.clone()) .name("trend-analyst") .enable_handoffs() .build().await?, ]; // Register shared specialists for agent in &agents { handoff_service.register_specialist( "data-expert", "Statistical analysis and data interpretation" ).await?; } // Phalanx: Parallel execution let phalanx = PhalanxService::new(); let results = phalanx.execute( agents, vec!["Analyze competitor X", "Research market Y", "Identify trend Z"] ).await?; }
API Reference
PaladinBuilder Methods
Autonomous Planning
#![allow(unused)] fn main() { /// Enable autonomous planning mode pub fn max_loops(mut self, loops: MaxLoops) -> Self // MaxLoops variants pub enum MaxLoops { Fixed(u32), // Manual loop count Auto, // Autonomous planning } }
Prompt Generation
#![allow(unused)] fn main() { /// Enable automatic prompt generation pub fn auto_generate_prompt(mut self, enabled: bool) -> Self /// Set agent description (required for auto-prompt) pub fn agent_description(mut self, description: impl Into<String>) -> Self /// Manual system prompt (overrides auto-generation) pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self }
Dynamic Temperature
#![allow(unused)] fn main() { /// Enable dynamic temperature adjustment pub fn dynamic_temperature(mut self, enabled: bool) -> Self /// Set temperature bounds (min, max) pub fn temperature_bounds(mut self, min: f32, max: f32) -> Self /// Manual temperature (overrides dynamic) pub fn temperature(mut self, temp: f32) -> Self }
Agent Handoffs
#![allow(unused)] fn main() { /// Enable agent handoff capabilities pub fn enable_handoffs(mut self) -> Self /// Set handoff strategy pub fn handoff_strategy(mut self, strategy: HandoffStrategy) -> Self /// Set maximum handoff depth pub fn max_handoff_depth(mut self, depth: u32) -> Self }
Configuration Types
AutonomousConfig
#![allow(unused)] fn main() { pub struct AutonomousConfig { pub planning: PlanningConfig, pub prompt_generation: PromptConfig, pub dynamic_temperature: TemperatureConfig, pub handoffs: HandoffConfig, } impl AutonomousConfig { pub fn new() -> Self; pub fn validate(&self) -> Result<(), String>; } }
PlanningConfig
#![allow(unused)] fn main() { pub struct PlanningConfig { pub enabled: bool, pub max_subtasks: u32, } impl PlanningConfig { pub fn new(max_subtasks: u32) -> Self; pub fn enabled() -> Self; } }
PromptConfig
#![allow(unused)] fn main() { pub struct PromptConfig { pub enabled: bool, pub description: Option<String>, } impl PromptConfig { pub fn new(description: String) -> Self; pub fn enabled() -> Self; pub fn with_description(self, description: String) -> Self; } }
TemperatureConfig
#![allow(unused)] fn main() { pub struct TemperatureConfig { pub enabled: bool, pub min: f32, pub max: f32, } impl TemperatureConfig { pub fn new(min: f32, max: f32) -> Self; pub fn enabled() -> Self; pub fn with_bounds(self, min: f32, max: f32) -> Self; } }
HandoffConfig
#![allow(unused)] fn main() { pub struct HandoffConfig { pub enabled: bool, pub strategy: HandoffStrategy, pub max_depth: u32, } impl HandoffConfig { pub fn new(strategy: HandoffStrategy, max_depth: u32) -> Self; pub fn enabled() -> Self; pub fn with_strategy(self, strategy: HandoffStrategy) -> Self; pub fn with_max_depth(self, max_depth: u32) -> Self; } }
Services
PlanningService
#![allow(unused)] fn main() { pub struct PlanningService { llm_port: Arc<dyn LlmPort>, } impl PlanningService { pub fn new(llm_port: Arc<dyn LlmPort>) -> Self; pub async fn generate_plan( &self, task: &str, max_subtasks: u32 ) -> Result<TaskPlan, PlanningError>; } }
PromptGenerationService
#![allow(unused)] fn main() { pub struct PromptGenerationService { llm_port: Arc<dyn LlmPort>, cache: Arc<RwLock<HashMap<String, String>>>, } impl PromptGenerationService { pub fn new(llm_port: Arc<dyn LlmPort>) -> Self; pub async fn generate_prompt( &self, agent_name: &str, description: &str ) -> Result<String, PromptError>; pub async fn clear_cache(&self); pub async fn invalidate_cache(&self, agent_name: &str, description: &str); } }
TemperatureService
#![allow(unused)] fn main() { pub struct TemperatureService { llm_port: Arc<dyn LlmPort>, } impl TemperatureService { pub fn new(llm_port: Arc<dyn LlmPort>) -> Self; pub async fn calculate_optimal_temperature( &self, task: &str, config: Option<&TemperatureConfig> ) -> Result<f32, TemperatureError>; pub async fn detect_task_type_with_llm( &self, task: &str ) -> Result<TaskType, TemperatureError>; } }
HandoffService
#![allow(unused)] fn main() { pub struct HandoffService { llm_port: Arc<dyn LlmPort>, specialists: Arc<RwLock<HashMap<String, String>>>, } impl HandoffService { pub fn new(llm_port: Arc<dyn LlmPort>) -> Self; pub async fn register_specialist( &self, name: &str, description: &str ) -> Result<(), HandoffError>; pub async fn should_handoff( &self, task: &str, current_agent: &str, context: &HandoffContext ) -> Result<HandoffDecision, HandoffError>; pub fn get_specialists(&self) -> Vec<String>; } }
Error Types
#![allow(unused)] fn main() { pub enum PlanningError { PlanGenerationFailed(String), EmptyPlan, CircularDependencies(Vec<String>), LlmError(LlmError), } pub enum PromptError { GenerationFailed(String), MissingDescription, InvalidLength { length: usize, min: usize, max: usize }, LlmError(LlmError), } pub enum TemperatureError { ClassificationFailed(String), InvalidBounds { min: f32, max: f32 }, LlmError(LlmError), } pub enum HandoffError { InvalidAgent(String), CircularHandoff { chain: Vec<String>, attempted_target: String }, MaxDepthExceeded { current_depth: u32, max_depth: u32 }, ExecutionFailed { agent: String, error: String }, LlmError(LlmError), } }
Examples
See the examples/ directory for complete working examples:
autonomous_planning.rs- Autonomous planning modeautonomous_prompt_generation.rs- Auto-generated promptsautonomous_temperature.rs- Dynamic temperatureautonomous_handoffs.rs- Agent delegationautonomous_complete.rs- All features combined
Run examples:
# Autonomous planning
cargo run --example autonomous_planning
# Auto-generate prompts
cargo run --example autonomous_prompt_generation
# Dynamic temperature
cargo run --example autonomous_temperature
# Agent handoffs
cargo run --example autonomous_handoffs
# All features
cargo run --example autonomous_complete
Further Reading
- Paladin Overview
- Battalion Orchestration
- Arsenal Tool System
- Garrison Memory System
- Configuration Guide
- API Documentation
Version: 0.1.0
Last Updated: February 1, 2026
Status: ✅ Stable (Epic 14 Complete)