AgentSession
The AgentSession manages conversation persistence. Every agent has a session that stores conversation history and metadata on disk automatically.
Creating Sessions
use picrust::session::AgentSession;
// Create new session
let session = AgentSession::new(
"unique-session-id", // Unique identifier
"assistant", // Agent type (for filtering)
"My Assistant", // Display name
"Helps with tasks", // Description
)?;
// Session is immediately persisted to disk at:
// ./sessions/unique-session-id/
Session Structure
Each session creates a directory:
./sessions/unique-session-id/
├── metadata.json # Session metadata
├── history.json # Conversation messages
└── debugger/ # Debug logs (if enabled)
├── api_request_1.json
├── api_response_1.json
├── tool_call_1.json
└── tool_result_1.json
pub struct SessionMetadata {
pub session_id: String,
pub agent_type: String,
pub name: String,
pub description: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub conversation_name: Option<String>,
pub parent_session_id: Option<String>,
pub parent_tool_use_id: Option<String>,
pub child_session_ids: Vec<String>,
pub custom_metadata: HashMap<String, serde_json::Value>,
pub model: Option<String>,
pub provider: Option<String>,
}
// From loaded session
let session = AgentSession::load("session-id")?;
println!("Created: {}", session.metadata().created_at);
println!("Type: {}", session.metadata().agent_type);
// Without loading full session
let metadata = AgentSession::get_metadata("session-id")?;
println!("Name: {}", metadata.name);
Store application-specific data:
// Through running agent
handle.set_custom_metadata("working_folder", "/path/to/project").await?;
handle.set_custom_metadata("user_preferences", serde_json::json!({
"theme": "dark",
"language": "en"
})).await?;
// Read it back
let folder = handle.get_custom_metadata("working_folder").await;
When an agent is running, always update metadata through the AgentHandle to avoid race conditions. The handle provides thread-safe access to the session.
Conversation History
Message Storage
Messages are stored as JSON:
[
{
"role": "user",
"content": "Hello"
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Hi! How can I help you?"
}
]
}
]
Reading History
// Get history without loading full session (efficient)
let history = AgentSession::get_history("session-id")?;
for message in history {
match message.role.as_str() {
"user" => println!("User: {:?}", message.content),
"assistant" => println!("Agent: {:?}", message.content),
_ => {}
}
}
Message Types
Messages can contain various content types:
use picrust::llm::{Message, ContentBlock};
// Simple text
let msg = Message::user("Hello");
// With multiple blocks
let msg = Message::user_with_blocks(vec![
ContentBlock::text("Analyze this code:"),
ContentBlock::text("fn main() { println!(\"Hello\"); }"),
]);
// Tool use (from agent)
let msg = Message::assistant_with_blocks(vec![
ContentBlock::text("I'll read that file for you."),
ContentBlock::tool_use("tool_123", "Read", serde_json::json!({
"file_path": "./README.md"
})),
]);
// Tool result (from tool execution)
let msg = Message::user_with_blocks(vec![
ContentBlock::tool_result("tool_123", "File contents...", false),
]);
Write-Through Persistence
Automatic Saving
Messages are written to disk immediately after each turn:
This ensures conversation state survives crashes.
Manual Saving
Force a save (rarely needed):
Useful if you’ve modified metadata directly.
Listing Sessions
List All Sessions
// Get all session IDs
let all_sessions = AgentSession::list_all()?;
for session_id in all_sessions {
println!("Session: {}", session_id);
}
List Top-Level Only
Filter out subagent sessions:
// Only get parent sessions
let top_level = AgentSession::list_top_level()?;
for session_id in top_level {
println!("Top-level session: {}", session_id);
}
More efficient when you need metadata:
// Returns Vec<(String, SessionMetadata)>
let sessions = AgentSession::list_with_metadata(true)?; // true = top-level only
for (session_id, metadata) in sessions {
println!("{}: {} ({})",
session_id,
metadata.name,
metadata.agent_type
);
println!(" Created: {}", metadata.created_at);
println!(" Is subagent: {}", metadata.is_subagent());
}
Loading Sessions
Load Existing Session
// Load complete session from disk
let session = AgentSession::load("session-id")?;
// Access history
let messages = session.history();
println!("Message count: {}", messages.len());
// Access metadata
let metadata = session.metadata();
println!("Agent type: {}", metadata.agent_type);
Check Existence
if AgentSession::exists("session-id") {
let session = AgentSession::load("session-id")?;
} else {
let session = AgentSession::new("session-id", "type", "name", "desc")?;
}
Parent-Child Relationships
Subagent Sessions
When agents spawn subagents, sessions are linked:
// Create child session
let child_session = AgentSession::new_subagent(
"child-session-id",
"researcher",
"Research Agent",
"Finds information",
"parent-session-id", // Parent session ID
"tool_use_123", // Parent tool use ID
)?;
// Child knows its parent
assert!(child_session.is_subagent());
assert_eq!(child_session.parent_session_id(), Some("parent-session-id"));
// Parent tracks children
let parent = AgentSession::load("parent-session-id")?;
assert!(parent.child_session_ids().contains(&"child-session-id".to_string()));
Use Cases
Parent-child relationships enable:
- Hierarchical agents: Parent delegates to specialized children
- Conversation trees: Track conversation branches
- Cleanup: Delete parent and all children together
- Tracing: Follow execution across agents
Conversation Naming
Automatic Naming
StandardAgent automatically generates conversation names after the first turn:
let config = AgentConfig::new("You are helpful")
.with_auto_name(true); // Enabled by default
// After first turn, session gets a name like:
// "Help with Rust debugging"
// "Build a web scraper"
// "Explain async/await"
Disable automatic naming:
let config = AgentConfig::new("You are helpful")
.with_auto_name(false);
Manual Naming
Set conversation name explicitly:
// Through running agent
handle.set_conversation_name("Debug Python script").await?;
// Or on session directly
session.set_conversation_name("My Conversation")?;
// Check if named
if session.has_conversation_name() {
println!("Name: {}", session.conversation_name().unwrap());
}
Custom Naming LLM
Use a cheaper/faster model for naming:
let naming_llm = llm.create_variant("claude-3-5-haiku-20241022", 1024);
let config = AgentConfig::new("You are helpful")
.with_naming_llm(naming_llm);
Session Management
Deleting Sessions
// Delete a session
session.delete()?;
// Session directory is removed from disk
Deleting a parent session does NOT automatically delete child sessions. Delete children first if needed.
When an agent is running, use the handle:
// Good: Thread-safe
if let Some(handle) = runtime.get("session-id").await {
handle.set_custom_metadata("key", "value").await?;
handle.set_conversation_name("New Name").await?;
}
// Bad: Race condition!
let mut metadata = SessionStorage::default().load_metadata("session-id")?;
metadata.set_custom("key", "value");
// Agent might overwrite this when it saves!
When agent is not running, direct access is safe:
use picrust::session::SessionStorage;
let mut metadata = SessionStorage::default().load_metadata("session-id")?;
metadata.set_custom("key", "value");
SessionStorage::default().save_metadata(&metadata)?;
Custom Storage Location
Change Storage Directory
use picrust::session::SessionStorage;
// Use custom directory
let storage = SessionStorage::with_dir("/custom/path/sessions");
// Create session with custom storage
let session = AgentSession::new_with_storage(
"session-id",
"type",
"name",
"desc",
storage,
)?;
Storage Interface
Implement custom storage backends:
pub trait SessionStore {
fn save_metadata(&self, metadata: &SessionMetadata) -> Result<()>;
fn load_metadata(&self, session_id: &str) -> Result<SessionMetadata>;
fn save_history(&self, session_id: &str, history: &[Message]) -> Result<()>;
fn load_history(&self, session_id: &str) -> Result<Vec<Message>>;
fn delete(&self, session_id: &str) -> Result<()>;
fn list_all(&self) -> Result<Vec<String>>;
}
Could implement:
- Database storage (PostgreSQL, SQLite)
- Cloud storage (S3, GCS)
- In-memory storage (testing)
- Encrypted storage
Best Practices
1. Unique Session IDs
use uuid::Uuid;
// Generate unique IDs
let session_id = Uuid::new_v4().to_string();
let session = AgentSession::new(&session_id, "type", "name", "desc")?;
2. Meaningful Names
// Good: Descriptive
let session = AgentSession::new(
&session_id,
"code-assistant",
"Python Debugger",
"Helps debug Python code",
)?;
// Bad: Generic
let session = AgentSession::new(
&session_id,
"agent",
"Agent",
"An agent",
)?;
3. Clean Up Old Sessions
// Delete sessions older than 30 days
let sessions = AgentSession::list_with_metadata(false)?;
for (session_id, metadata) in sessions {
let age = Utc::now() - metadata.created_at;
if age.num_days() > 30 {
if let Ok(session) = AgentSession::load(&session_id) {
session.delete()?;
}
}
}
4. Backup Sessions
use std::fs;
// Backup session directory
let source = "./sessions/session-id";
let backup = "./backups/session-id";
fs::copy(source, backup)?;
5. Monitor Disk Usage
use walkdir::WalkDir;
fn get_sessions_size() -> u64 {
WalkDir::new("./sessions")
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file())
.filter_map(|e| e.metadata().ok())
.map(|m| m.len())
.sum()
}
Advanced Patterns
Session Migrations
// Migrate old session format
let old_session = load_old_format("session-id")?;
let new_session = AgentSession::new(
&old_session.id,
&old_session.type_,
&old_session.name,
&old_session.description,
)?;
// Migrate history
for message in old_session.messages {
new_session.add_message(convert_message(message))?;
}
new_session.save()?;
Session Templates
fn create_code_assistant_session(id: &str) -> Result<AgentSession> {
let session = AgentSession::new(
id,
"code-assistant",
"Code Assistant",
"Helps with coding tasks",
)?;
// Set default metadata
session.set_custom_metadata("preferred_language", "rust")?;
session.set_custom_metadata("max_file_size", 1024 * 1024)?;
Ok(session)
}
Session Cloning
// Clone session for branching conversations
let original = AgentSession::load("original-session")?;
let clone = AgentSession::new(
"cloned-session",
&original.metadata().agent_type,
&format!("{} (clone)", original.metadata().name),
&original.metadata().description,
)?;
// Copy history
for message in original.history() {
clone.add_message(message.clone())?;
}
clone.save()?;
Next Steps
Message Flow
Understand input/output communication patterns
Streaming & History
Critical dual-channel architecture
AgentSession API
Complete session API reference
Custom Storage
Implement custom storage backends