Thinker
Prompt construction engine with layer-based architecture, streaming state machine, and provider-optimized output formatting.
The thinker module is Aleph's prompt construction engine. It builds the system prompt and manages the conversation context sent to the LLM. It uses a layer-based architecture where each layer contributes a section to the final prompt.
Design Philosophy
- Layer-based composition — Each concern (identity, skills, memory, tools) is a separate layer
- Provider-specific optimization — Different LLM providers receive differently formatted prompts
- Streaming state machine — Handles partial tokens from streaming responses
Architecture
┌─────────────────────────────────────────────┐
│ Thinker │
├─────────────────────────────────────────────┤
│ │
│ Layer 1: Identity (who the agent is) │
│ Layer 2: Skills (available capabilities) │
│ Layer 3: Memory (relevant context) │
│ Layer 4: Tools (available tools) │
│ Layer 5: Rules (behavior guidelines) │
│ Layer 6: Conversation (message history) │
│ │
└─────────────────────────────────────────────┘Core Components
Thinker
Main prompt builder:
pub struct Thinker {
layers: Vec<Box<dyn PromptLayer>>,
provider: ProviderType,
}
impl Thinker {
pub async fn build_prompt(
&self,
context: &ThinkerContext,
) -> Result<String> {
let mut prompt = String::new();
for layer in &self.layers {
let section = layer.render(context).await?;
prompt.push_str(§ion);
}
Ok(prompt)
}
}PromptLayer Trait
#[async_trait]
pub trait PromptLayer: Send + Sync {
async fn render(
&self,
context: &ThinkerContext,
) -> Result<String>;
fn priority(&self) -> i32;
}Built-in layers:
IdentityLayer— Agent name, personality, communication styleSkillsLayer— Available skills and triggersMemoryLayer— Relevant memories from the knowledge baseToolsLayer— Tool descriptions and schemasRulesLayer— Behavioral rules and redlinesConversationLayer— Recent message history
Streaming State Machine
Handles partial tokens from LLM streaming:
pub struct BlockState {
buffer: String,
current_block: Option<BlockType>,
}
impl BlockState {
pub fn feed(&mut self,
token: &str,
) -> Vec<BlockEvent> { /* ... */ }
}Block types:
Text— Plain text outputToolCall— Tool invocation blockCode— Code fence blockThinking— Chain-of-thought block
Prompt Sanitizer
Cleans prompts before sending to the LLM:
pub fn sanitize(
prompt: &str,
) -> StringRemoves:
- Duplicate system prompts
- Excessive whitespace
- Truncated tool definitions
Provider-Specific Formatting
Different providers receive different prompt formats:
OpenAI:
- System message + user/assistant alternating
- Tool definitions in
toolsarray - Function calling format
Anthropic:
- XML-style tags for structure
\n\nHuman:/\n\nAssistant:delimiters- Tool use in
<function_calls>blocks
Ollama / Local:
- Simple text prompts
- Minimal formatting overhead
Soul
The soul.rs module manages the agent's persistent identity:
pub struct Soul {
name: String,
personality: String,
values: Vec<String>,
}The soul is loaded from ~/.aleph/soul.md and injected into every prompt via the IdentityLayer.
User Profile
Per-user customization:
pub struct UserProfile {
preferences: HashMap<String, String>,
custom_instructions: String,
}User profiles override default behavior without modifying the soul.
Safety Properties
- UTF-8 safe — All string slicing uses
.get(..n).unwrap_or_default() - Deterministic ordering — Provider list sorted by ID
- No lock issues — Uses
tokio::synclocks with recovery - No
static mut— UsesOnceLockandLazyLock
Code Location
src/thinker/mod.rs—Thinkerand layer orchestrationsrc/thinker/layers/— Prompt layer implementationssrc/thinker/streaming/— Streaming state machinesrc/thinker/soul.rs— Agent identitysrc/thinker/user_profile.rs— User customizationsrc/thinker/prompt_sanitizer.rs— Prompt cleaning
See Also
- Prompt System — Prompt concepts
- Memory — Knowledge base integration
- Skills — Skill prompt injection