Garrison Memory
The Garrison is Paladin AI's conversation memory system. When attached to a Paladin it stores and retrieves conversation history, giving the agent context across multiple reasoning loops and between invocations.
Garrison is defined in crates/paladin-ports/src/output/garrison_port.rs (the GarrisonPort
trait) with adapter implementations in crates/paladin-memory/src/garrison/.
Table of Contents
- Concepts
- Quick Start
- Garrison Adapters
- GarrisonPort Trait
- GarrisonConfig
- Conversation Roles
- Attaching to a Paladin
- Long-Term Memory with Embeddings
- config.yml Reference
- Error Handling
- Best Practices
Concepts
| Term | Definition |
|---|---|
| Garrison | The memory subsystem; stores conversation entries |
| GarrisonEntry | A single message with role, content, timestamp, and optional token count |
| ConversationRole | User, Assistant, System, or Tool |
| GarrisonConfig | Window size, token budget, and eviction strategy |
| GarrisonStats | Entry count, total token count, optional storage size |
Quick Start
use paladin_memory::garrison::InMemoryGarrison;
use paladin_core::platform::container::garrison::{GarrisonConfig, GarrisonEntry, ConversationRole};
use paladin_ports::output::garrison_port::GarrisonPort;
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let garrison = Arc::new(InMemoryGarrison::new(GarrisonConfig::default()));
// Store a user message
garrison.remember(GarrisonEntry::new(
ConversationRole::User,
"What is Rust's ownership model?".to_string(),
)).await?;
// Store the assistant reply
garrison.remember(GarrisonEntry::new(
ConversationRole::Assistant,
"Rust's ownership model ensures memory safety without a GC...".to_string(),
)).await?;
// Recall last 10 entries
let history = garrison.recall_recent(10).await?;
for entry in &history {
println!("{:?}: {}", entry.role, entry.content);
}
// Search by keyword
let results = garrison.search("ownership", 5).await?;
println!("Found {} messages about ownership", results.len());
Ok(())
}
Garrison Adapters
Both adapters are in crates/paladin-memory/src/garrison/.
InMemoryGarrison
| Property | Value |
|---|---|
| Persistence | None (process-scoped) |
| Performance | O(1) write, O(N) search |
| Use case | Development, testing, short-lived sessions |
use paladin_memory::garrison::InMemoryGarrison;
use paladin_core::platform::container::garrison::GarrisonConfig;
let garrison = InMemoryGarrison::new(GarrisonConfig::default());
SqliteGarrison
| Property | Value |
|---|---|
| Persistence | SQLite file (survives restarts) |
| Performance | O(log N) indexed read, FTS5 full-text search |
| Use case | Single-agent production deployments |
use paladin_memory::garrison::SqliteGarrison;
use paladin_core::platform::container::garrison::GarrisonConfig;
let garrison = SqliteGarrison::connect(
"./garrison.db",
GarrisonConfig::default(),
"my-paladin-id",
).await?;
SqliteGarrison::connect() creates the file and runs migrations automatically.
GarrisonPort Trait
Full interface defined in crates/paladin-ports/src/output/garrison_port.rs:
#[async_trait]
pub trait GarrisonPort: Send + Sync {
/// Store a new conversation entry
async fn remember(&self, entry: GarrisonEntry) -> Result<(), GarrisonError>;
/// Retrieve the N most recent entries (newest last)
async fn recall_recent(&self, limit: usize) -> Result<Vec<GarrisonEntry>, GarrisonError>;
/// Full-text search across stored entries
async fn search(&self, query: &str, limit: usize) -> Result<Vec<GarrisonEntry>, GarrisonError>;
/// Clear all entries from this garrison
async fn forget_all(&self) -> Result<(), GarrisonError>;
/// Get storage statistics
async fn stats(&self) -> Result<GarrisonStats, GarrisonError>;
}
GarrisonStats
pub struct GarrisonStats {
pub entry_count: usize, // Total stored entries
pub total_tokens: u32, // Cumulative token count
pub size_bytes: Option<u64>, // Storage size (adapters may not support this)
}
GarrisonConfig
use paladin_core::platform::container::garrison::GarrisonConfig;
// max_entries: window size; max_tokens: token budget per context window
let config = GarrisonConfig::new(200, Some(8000));
| Field | Default | Description |
|---|---|---|
max_entries | 100 | Maximum entries to retain in the window |
max_tokens | None | Optional token budget; triggers eviction when exceeded |
eviction_strategy | Oldest | Oldest removes oldest entries when window is full |
Conversation Roles
use paladin_core::platform::container::garrison::ConversationRole;
ConversationRole::User // Human turn
ConversationRole::Assistant // Paladin / LLM turn
ConversationRole::System // System instruction
ConversationRole::Tool // Tool call result
Attaching to a Paladin
use paladin::application::services::paladin::paladin_builder::PaladinBuilder;
use paladin_memory::garrison::SqliteGarrison;
use paladin_core::platform::container::garrison::GarrisonConfig;
use paladin_ports::output::garrison_port::GarrisonPort;
use std::sync::Arc;
let garrison: Arc<dyn GarrisonPort> = Arc::new(
SqliteGarrison::connect("./memory.db", GarrisonConfig::default(), "agent-1").await?
);
let paladin = PaladinBuilder::new(llm_port)
.system_prompt("You are a persistent memory assistant.")
.with_garrison(garrison)
.build()
.await?;
Once attached, the Paladin automatically:
- Retrieves recent history before each LLM call.
- Appends the user turn and assistant response after each loop.
Long-Term Memory with Embeddings
The LongTermGarrisonPort trait (also in garrison_port.rs) extends GarrisonPort with
semantic similarity search using vector embeddings:
pub trait LongTermGarrisonPort: GarrisonPort {
async fn remember_with_embedding(
&self,
entry: GarrisonEntry,
embedding: Vec<f32>,
) -> Result<(), GarrisonError>;
async fn search_similar(
&self,
query_embedding: Vec<f32>,
limit: usize,
) -> Result<Vec<GarrisonEntry>, GarrisonError>;
}
For full vector-based semantic memory, consider Sanctum — see Sanctum Vector Memory.
config.yml Reference
garrison:
type: sqlite # "in_memory" or "sqlite"
path: ./garrison.db # SQLite only
max_entries: 100
max_tokens: 8000 # Optional token budget
eviction_strategy: oldest # "oldest" (default)
Error Handling
GarrisonError variants:
| Variant | Cause | Recovery |
|---|---|---|
StorageError(String) | Database / IO failure | Check path, permissions, disk space |
SerializationError(String) | Corrupt entry data | Clear and rebuild garrison |
TokenizationError(String) | Token counting failure | Check tokenizer config |
NotFound | Entry missing | Expected after forget_all() |
Best Practices
- Always use
SqliteGarrisonin production —InMemoryGarrisonloses all history when the process restarts. - Set
max_tokensto stay within the LLM's context window; large histories degrade performance. - Use one Garrison per Paladin — shared garrisons across multiple agents mix conversation contexts and confuse the LLM.
- Call
forget_all()between sessions if context carry-over is undesirable (e.g., fresh chat sessions). - Use
search()to retrieve relevant past entries rather than dumping the full history into the prompt.