Thinker (Prompt Engine)
Thinker is Aleph's prompt construction engine: ~28 fine-grained PromptLayers with priority + the stable_summary / live_status dual-track cache discipline, plus the prompt_contract reachability / ratchet checks.
The thinker module is Aleph's prompt construction engine. It assembles the SYSTEM prompt at every turn and maintains the runtime memory (recall_context) and transient tail (transient_recall) in parallel. After Round 9 it uses ~28 fine-grained PromptLayers instead of the old 6-layer mental model; each layer has a priority and a short contract, assembled by prompt_pipeline.
Design Philosophy
- Fine-grained layers — ~28 independent PromptLayers each own one concern (
agent_role/environment/mcp_instructions/extra_files/guidelines/security/voice_mode/graph_topology/curated_memory/recall_context/ ...). R10 ("intelligence lives in the prompt") demands each layer is independently reviewable. - Priority ordering — every layer exposes a
priority()integer; the pipeline renders in ascending order so cache-friendly (unchanging) content sits earlier and dynamic (timestamped / counted) content sits later. - Stable / live dual track — the SYSTEM-prompt portion (
stable_summary) is the cache prefix and never carries countdowns; live countdowns (deadline / next-wake) ride the transient tail message, so the cache READ 0.1× / WRITE 1.25× asymmetry is not triggered by countdown byte changes. - Provider-specific rendering — the same content is rendered differently for OpenAI / Anthropic / Ollama;
thinker::prompt_pipelinereads the provider name and picks the matchingRenderStyle(XML / fenced / inline) to assemble. - Contract guards —
prompt_contract::reachable_layersguarantees every layer either appears when enabled or carries a reason onto the whitelist;scaffold_bytes_ratchetonly ever shrinks the byte count (any upward move requires an explicit commit-time justification);aleph-server prompt-sizereports the live byte count.
Architecture
┌─────────────────────────────────────────────┐
│ Thinker │
├─────────────────────────────────────────────┤
│ │
│ Priority 0–800: static identity / config / knowledge
│ Priority 800–1500: static / cache-friendly
│ Priority 1500–1720: static / slow-changing
│ Priority 1720: RuntimeContextLayer (dynamic
│ — token / counter / session name)
│ Priority >1720: transient recall (transient
│ tail only)
└─────────────────────────────────────────────┘PromptPipeline::assemble(provider, input) renders every enabled layer in priority order into a String injected as SYSTEM prompt; the transient_recall portion takes a separate path through HarnessDeps::recall_context and never enters SYSTEM.
Core Components
PromptLayer Trait
#[async_trait]
pub trait PromptLayer: Send + Sync {
fn name(&self) -> &'static str;
fn priority(&self) -> i32;
async fn render(&self, input: &ThinkerContext) -> Result<String>;
/// Default inferred by contract.rs; when a layer shouldn't appear
/// it returns false and goes on the "reason whitelist" path.
fn is_reachable(&self, input: &ThinkerContext) -> bool { true }
}Each layer owns:
priority(): assembly order; within the same priority the order is stable / deterministicrender(&ctx): pure function / IO-free (no LLM calls allowed); failure degrades to "this layer is omitted" with a warnis_reachable(&ctx): defaulttrue; a layer whose config disables it or that is not on the whitelist returnsfalseand a reason is recorded onprompt_contract::reachable_layerswhitelist
Built-in Layer Inventory (after Round 9, ~28 layers)
Ascending by priority:
| Layer | Priority | Role |
|---|---|---|
agent_role | 0 | agent identity / behavior contract (R8 source) |
profile | 30 | current agent profile |
soul | 60 | soul.md injection |
identity_files | 90 | identity files (CLAUDE.md / soul.md / ...) |
mcp_instructions | 120 | MCP integration instructions |
environment | 150 | runtime env summary (OS / cwd / model) |
runtime_capabilities | 180 | current provider's capabilities |
runtime_context | 1720 | the only dynamic SYSTEM layer: token count / terminate reason / session name |
curated_memory | 800 | stable segment of MemoryEnvelope |
recall_context | transient tail | transient_recall message (NOT in SYSTEM) |
graph_topology | 750 | governance graph render (governed sessions only) |
voice_mode | 1710 | voice-mode prompt (voice turns only) |
multi_step_conduct | 300 | multi-step behavior rules |
operational_guidelines | 350 | operational guardrails |
guidelines | 400 | general rules |
security | 450 | redlines / safety policies |
mcp_protocol | 200 | MCP protocol instructions |
provider_guidance | 500 | provider-specific render hints |
skill_instructions | 550 | skill SKILL.md injection |
tool_runtime_state | 600 | tool runtime state |
protocol_tokens | 650 | protocol tokens |
citation_standards | 700 | citation standards |
standing_goal | 720 | current standing goal (if any) |
memory_protocol | 730 | memory protocol instructions |
plan_prompt | 880 | current welded Strategy render |
extra_files | 920 | current session's extra files |
execution_plan | 950 | current scratchpad execution list |
doctor_repair_hint | 1100 | /doctor repair hint (conditional) |
session_context_guide | 780 | session-level guidance |
RuntimeContextLayeris the only layer in SYSTEM carrying per-turn digits (current_total_tokens/terminate_reason/session_key) — every turn changes them, so it must be the last layer in SYSTEM to minimize cache-rewriting cost; all other 27 layers are cache-friendly content and sit strictly before 1720.
ThinkerContext
pub struct ThinkerContext {
pub session_key: SessionKey,
pub agent_id: AgentId,
pub voice: Option<VoiceContext>, // voice turn context
pub running: RunningStats, // runtime stats (turns / tokens / time)
pub profile: Option<ProfileView>,
pub skills: Vec<SkillView>,
pub goals: Vec<GoalView>,
pub strategies: Vec<StrategyView>,
pub diagnostics: Vec<DiagnosticView>, // from doctor
pub memory: Option<MemoryEnvelope>, // already-recalled envelope
pub session_recent: Vec<ChatMessageView>, // current session recent
pub plan: Option<PlanView>, // current welded Strategy render
pub vault: Option<VaultView>, // vault secrets (encrypted)
pub mcp: Option<McpView>, // current session's active MCP servers
pub todo: Option<TodoView>, // current execution list
pub runtime: RuntimeView, // OS / cwd / model / counters
pub scratchpad: ScratchpadView, // execution list + objective
}render(&ctx) reads this context, renders what it cares about, and never does IO. VoiceContext is injected by voice_mode::get(session_key); MemoryEnvelope is assembled by MemoryContextProvider::build_envelope; PlanView is pulled by StrategyLayer; TodoView is pulled by ScratchpadLayer.
PromptPipeline Assembly
PromptPipeline::assemble(provider, input) iterates layers in priority-ascending order, calls each layer.render(&input), and joins the result with newlines into a single string. Failed layers log a warn but don't abort (fail-soft); prompt_contract validates that every enabled layer either appears or is on the "reason whitelist".
pub struct PromptPipeline {
layers: Vec<Arc<dyn PromptLayer>>,
}Construction lives in bin/aleph-server/commands/start/builder/constructor/mod.rs::build_thinker_pipeline:
PromptPipeline::new(vec![
Box::new(AgentRoleLayer), // priority 0
Box::new(ProfileLayer), // priority 30
Box::new(SoulLayer), // priority 60
Box::new(IdentityFilesLayer), // priority 90
Box::new(McpInstructionsLayer), // priority 120
Box::new(EnvironmentLayer), // priority 150
Box::new(McpProtocolLayer), // priority 200
Box::new(RuntimeCapabilitiesLayer), // priority 180
Box::new(MultiStepConductLayer), // priority 300
Box::new(OperationalGuidelinesLayer), // priority 350
Box::new(GuidelinesLayer), // priority 400
Box::new(SecurityLayer), // priority 450
Box::new(ProviderGuidanceLayer), // priority 500
Box::new(SkillInstructionsLayer), // priority 550
Box::new(ToolRuntimeStateLayer), // priority 600
Box::new(ProtocolTokensLayer), // priority 650
Box::new(CitationStandardsLayer), // priority 700
Box::new(StandingGoalLayer), // priority 720
Box::new(MemoryProtocolLayer), // priority 730
Box::new(GraphTopologyLayer), // priority 750
Box::new(CuratedMemoryLayer), // priority 800
Box::new(PlanLayer), // priority 880
Box::new(ExtraFilesLayer), // priority 920
Box::new(ExecutionPlanLayer), // priority 950
Box::new(DoctorRepairHintLayer), // priority 1100
Box::new(VoiceModeLayer), // priority 1710
Box::new(RuntimeContextLayer), // priority 1720
])Priority 1720 is last on purpose — RuntimeContextLayer is the only dynamic SYSTEM layer (per-turn digits), every other 27 layer is cache-friendly content and sits strictly before 1720. Anthropic's prompt cache is prefix-keyed, so a single byte change forces the entire conversation prefix to be re-WRITTEN at 1.25× cost instead of READ at 0.1×.
The Two Rulers (src/thinker/prompt_contract.rs)
R10's "intelligence in the prompt, not in the loop" gets two explicit metric rulers:
pub const fn reachable_layers(pipeline: &PromptPipeline) -> Vec<(&str, bool, Option<String>)>;
pub fn scaffold_bytes_ratchet(input: &ThinkerContext) -> Result<usize, ContractError>;reachable_layers— list every enabled layer; a layer withis_reachable == falsemust carry a reason onto the whitelist (whitelist_disabled(name, reason)), otherwise the test fails.scaffold_bytes_ratchet— compute the currentThinkerContext's SYSTEM-prompt scaffold-only byte count (excludingRuntimeContext/recall_context/transient_recall), compare to the last recorded value, only allow shrinking; any increase must be explicitly whitelisted.prompt-sizeCLI subcommand (aleph-server prompt-size): reports the current scaffold byte count + per-layer byte distribution + remaining headroom to the R10 redline CEILING.
prompt-sizeis also used by the loop / goal continuation prompt path (continuation callsbuild_system_promptwhich runs the same contract); the R10 redline CEILING is one shared byte budget for both harness lines and SYSTEM prompt.
prompt_pipeline (introduced in Round 9)
PromptPipeline replaces the old Thinker::build_prompt, unifying the ~28 layers:
pub struct PromptPipeline {
layers: Vec<Box<dyn PromptLayer>>,
}
impl PromptPipeline {
pub fn new(layers: Vec<Box<dyn PromptLayer>>) -> Self;
pub fn assemble(&self, input: &ThinkerContext) -> Result<String>;
pub fn with_layers(self, layers: Vec<Box<dyn PromptLayer>>) -> Self;
}ProviderRenderStyle (xml / fenced / inline) decides per-layer wrapping by provider — PromptPipeline::render_section(layer, ctx, style) is the single chokepoint.
Streaming State Machine
thinker::streaming provides the per-token state machine BlockState:
pub struct BlockState {
pub buffer: String,
pub current_block: Option<BlockType>,
}
impl BlockState {
pub fn feed(&mut self, token: &str) -> Vec<BlockEvent> { /* ... */ }
}BlockType has five variants: Text / ToolCall / Code / Thinking / Image. BlockState::feed parses an incoming token and emits BlockEvents (new block / current block continues / current block ends). The Panel-side renders the two-stage caption with this state machine (see interfaces/webchat/src/views/chat/state/streaming.rs).
Provider-Specific Rendering
Different providers parse the same content differently — Anthropic wants XML, OpenAI wants fenced code, Ollama wants plain text. PromptLayer::render returns plain text (without <example> / ``` wrapping); the wrapping is decided by PromptPipeline::render_section per ProviderRenderStyle:
pub enum ProviderRenderStyle { Xml, Fenced, Inline }- Anthropic / XML: each layer is wrapped in
<section name="<layer_name>">…</section>; inner content is not re-escaped - OpenAI / Fenced: each layer is wrapped in
```<layer_name>\n…\n```;```inside content must be re-escaped - Ollama / Inline: each layer is plain text with a single-line
--- <layer_name> ---heading
style_for_provider(provider_name) is called once at pipeline assembly; the result is cached on the pipeline struct.
Key Source Files
src/thinker/mod.rs— module entry, re-exportsPromptPipeline/ThinkerContext/PromptLayersrc/thinker/prompt_pipeline.rs—PromptPipelineand layer assemblysrc/thinker/prompt_contract.rs—reachable_layers/scaffold_bytes_ratchet/whitelist_disabledsrc/thinker/prompt_layer.rs—PromptLayertrait +Box<dyn PromptLayer>wrappingsrc/thinker/render_style.rs—ProviderRenderStylethree variantssrc/thinker/streaming.rs—BlockStatestreaming state machinesrc/thinker/soul.rs— soul.md parsingsrc/thinker/identity_files.rs— identity files injectionsrc/thinker/context.rs—ThinkerContextfield definitionssrc/thinker/runtime_context.rs—RuntimeContextLayer(priority 1720, the only dynamic SYSTEM layer)src/thinker/nudges.rs— 9 model-facing prompt strings (MAX_STEPS_HINT/MAX_OUTPUT_TOKENS_RESUME_NUDGE/INTERRUPTION_NOTEetc.), centralized after the harness purged themsrc/thinker/security_context.rs—SecurityLayersrc/thinker/curated_memory.rs—CuratedMemoryLayer(assembles the stable segment ofMemoryEnvelope)src/thinker/recall_context.rs—RecallContextand transient-tail assemblysrc/thinker/project_instructions.rs— various layer helperssrc/thinker/xml_util.rs— XML escaping / wrappingsrc/thinker/interaction.rs—InteractionManifestchannel-capability awarenesssrc/thinker/prompt_budget.rs—PromptBudget(cap layer count per provider)src/thinker/layers/{mod,voice_mode,graph_topology,curated_memory,doctor_repair_hint,multi_step_conduct,operational_guidelines,security,runtime_capabilities,protocol_tokens,citation_standards,plan,standing_goal,plan_prompt,execution_plan,extra_files,profile,provider_guidance,guidelines,environment,tool_runtime_state,security_session,supervisor_verification,tool_layer}.rs— 20+ concrete layers
26.7.x Addendum
Round 9 Layer Reorganization
26.7.x split the old 6-layer mental model (Identity / Skills / Memory / Tools / Rules / Conversation) into ~28 fine-grained PromptLayers, each independently reviewable, individually disable-able, and whitelist-able. R10 ("intelligence in the prompt, not in the loop") requires every layer to have its own contract and priority.
Two Rulers (R10 Redline)
prompt_contract::reachable_layers + scaffold_bytes_ratchet turn the R10 redline from "hand-calculated" into code-measurable. aleph-server prompt-size reports live bytes + per-layer distribution, putting R10 within reach. The CEILING ratchet is shared between harness and thinker: the SYSTEM-prompt byte ceiling and the_harness_line_budget_does_not_grow's CEILING are the same ruler (R10's single byte budget for two subsets).
Voice Mode Layer
26.7.x VoiceModeLayer (priority 1710) injects voice-turn prompt rules — including "fix ASR-transcribed homophones" and "preserve spoken filler as user pragmatic signal". The VoiceContext field is injected by voice_mode::get() (active-only; the transcribed flag is set by inbound_router).
transient_recall & Cache Friendliness
26.7.1+: dual-track design — stable MemoryEnvelope goes into the SYSTEM prompt (cache-friendly), live countdowns (deadline / next-wake) go into the transient tail message (past the cache breakpoint, byte changes only cost themselves). Mirrors looping::LoopState's stable_summary vs live_status — the same design, thinker and looping share the R10 cache discipline.
Prompt Cache Discipline
RuntimeContextLayer is deliberately last (priority 1720) — the only SYSTEM-prompt layer that carries per-turn digits (token count / terminate reason / session_key). All other ~27 layers are cache-friendly content (identity / profile / soul / environment / security / plan etc.) and sit strictly before 1720. Anthropic's prompt cache is prefix-keyed, so a single byte change forces the entire conversation prefix to be re-WRITTEN at 1.25× cost instead of READ at 0.1× — a single countdown byte change would wipe the cache benefit for the whole conversation.
See Also
- R10 Thin Harness Redline — the five "Don'ts"
- Memory System —
curated_memory/recall_contextdual-track source - Skill System —
skill_instructionslayer source - Architecture: Harness section — layer assembly location
- Thinker source — full layer list
- R10 byte-budget test — shared
CEILING = 5055ruler
Gateway
WebSocket JSON-RPC gateway for client connections, session management, execution engine, and multi-interface support.
Memory System
Aleph's long-term memory: raw_memories (L0) + notes (L1) two-layer architecture, hybrid retrieval, and dream daemon. Includes SkillOpt self-evolution, wikilink lifecycle, Leiden refinement, and [[wikilink]] supersession.