Aleph
Architecture

Thinker

Prompt assembly with 36 layers (19 Stable / 17 Dynamic), cache-zone split, scaffold-byte ratchet, and UnifiedMessage streaming.

Thinker

The Thinker owns all LLM interactions and prompt assembly. The sole public entry point is thinker::PromptBuilder, which wraps a PromptPipeline. The old agent_loop::PromptBuilder was deleted during the harness migration (Phase 6/7).

Location: src/thinker/


Components

ComponentFilePurpose
PromptBuilderprompt_builder/Build system prompts for sub-agent and main-loop paths
PromptPipelineprompt_pipeline.rsComposable prompt-assembly engine over a sorted layer list
PromptLayerprompt_layer.rsTrait for individual layers; declares priority / paths / stability
PromptConfigprompt_builder.rsUser-tunable prompt knobs (language, extra_files, …)
InteractionManifestinteraction.rsChannel capability awareness (paradigm + capabilities + constraints)
SecurityContextsecurity_context.rsPolicy-driven permissions
ContextAggregatorcontext.rsReconcile interaction + security into ResolvedContext
PromptModeprompt_mode.rsFull / Compact / Minimal mode filter
IdentityFilesidentity_files.rsLoaded SOUL.md / IDENTITY.md / AGENTS.md / TOOLS.md / HEARTBEAT.md
SoulManifestsoul.rsSOUL.md structured parser (used by identity.get preview)
MemoryContextProvidermemory_context_provider.rsMemory envelope + orientation + recall-message builder
UnifiedMessage / ProviderDeltasrc/providers/message.rsLLM-agnostic message + streaming delta
MessageAssemblersrc/providers/message_assembler.rsSingle reducer that owns the assembled message + strips inline <think> live

PromptBuilder

// Sub-agent inline prompt (flat string, no cache split)
let prompt = builder.build_system_prompt(&tools);

// Main-loop prompt split into cacheable Stable prefix + Dynamic suffix
let prompt = builder.build_system_prompt_cached_with_mode(&tools, &soul, profile, PromptMode::Full);

// Agent definition (sub-agent role header + protocol blocks)
let prompt = builder.build_for_agent(&agent_def, &tools, &soul);

The main-loop path emits a SystemPromptPart Stable prefix (cacheable by the LLM provider) and a marker-free Dynamic tail. The Cached path is the production entry point; the Basic path is for inline sub-agent prompts.


36 Layers

PromptPipeline::default_layers() registers 36 layers in priority order, anchored by test_default_layers_count (assert_eq!(pipeline.layer_count(), 36)). 19 sit in the Stable prefix zone (priority < 1700, eligible for section-level caching) and 17 sit in the Dynamic suffix zone (priority >= 1700, never cached). The split is locked by stable_layers_come_before_dynamic.

Stable Zone (Priority < 1700)

Content rarely changes — eligible for provider section-level caching.

PriorityLayerNotes
50SoulLayer~/.aleph/agents/{id}/SOUL.md
55AgentRoleLayerSub-agent role header + protocol blocks
60CuratedMemoryLayerPre-rendered <CuratedMemory> + <UserProfile> envelope
70StrategyLayerActive StraTA strategy (welded <strategy> envelope)
75ChainContextLayerSubagent delegation depth (root ⇒ silent)
100ProfileLayerAGENTS.md workspace overlay
200RoleLayerBase assistant role
300EnvironmentLayerProcess-invariant half (Stable facts: machine / OS)
400RuntimeCapabilitiesLayerDetected Python / Node / FFmpeg runtimes
502ToolRuntimeStateLayerSnapshot of the 30s-TTL tool-health cache
600SecurityLayerCross-tool safety posture (Static zone facts only)
605ProtocolTokensLayerBackground-paradigm JSON-RPC protocol tokens
700OperationalGuidelinesLayerBackground operational rules
800MultiStepConductLayerAutonomous scratchpad planning + interactive progress narration
810ProviderGuidanceLayermodel_behaviors/{family}.md baseline + delta
900SessionBudgetLayerResolved per-run iteration_cap
950CitationStandardsLayerCitation formatting
1050SkillInstructionsLayerActive skill instructions
1100SpecialActionsLayerSelf-correction logging + flagging
1300GuidelinesLayerGeneral guidelines
1600LanguageLayer[general] language configured response language

Dynamic Zone (Priority ≥ 1700)

Per-request content — never cached. 17 layers (pinned by dynamic_names.len() == 17).

PriorityLayerNotes
1701RuntimeContextLayerPer-run varying half (cwd, time) — separate from EnvironmentLayer
1702ToolRuntimeStateLayer was 502; moved to Dynamic so probe flips don't invalidate the cached prefix
1715DoctorRepairHintLayerWebRich paradigm + failing doctor check
1720VoiceModeLayerVoice-transcribed turn
1725McpInstructionsLayerConnected MCP server instructions
1730IdentityFilesLayerIDENTITY.md / TOOLS.md / HEARTBEAT.md
1735ExtraFilesLayer[prompt.extra_files]
1740MemoryProtocolLayerCross-tool memory-write guidance
1750TimerLoopLayerActive watch loop status
1753GraphTopologyLayerLoop-graph governance topology
1754StandingGoalLayerActive standing goal
1755ExecutionPlanLayerActive scratchpad plan with at least one item
1756StrategyPointerLayerStrategy guardrails echoed near the read head
1757SessionContextGuideLayerHistory carrying compaction summaries
1758OperatingEnvelopeLayerApproval tier + usage mode (Dynamic so a composer pill flip does not invalidate the prefix)
1760AgentCatalogLayerAvailable sub-agent catalog
1799ProviderGuidanceLayer runtime deltaPer-request behavior_name resolution

Layers removed as dead injection surfaces: ToolsLayer (text-schema fallback; native tool_use always wins), ToolUsageGrammarLayer, ThinkingGuidanceLayer, GenerationModelsLayer, CustomInstructionsLayer, InboundContextLayer, SessionResumeLayer, McpResourceIndexLayer, McpToolIndexLayer, HydratedToolsLayer, SkillModeLayer, HeartbeatLayer, ResponseFormatLayer, MemoryAugmentationLayer. The three R9 ratchets (reachable_layers, scaffold_bytes_ratchet, no_sentence_is_stated_twice, no_environment_fact_is_stated_twice) pin the surviving set.


Assembly Paths

AssemblyPath has exactly two variants — phantom paths are how layers go silently missing, so each variant has one entry point.

PathDescription
BasicInline sub-agent prompt — one flat string, no cache split. Entry point: PromptBuilder::build_system_prompt (subagent_spawner).
CachedMain-loop prompt — split into Stable cacheable prefix + Dynamic suffix. Entry point: PromptBuilder::build_system_prompt_cached_with_mode (harness_bridge::prompt_build).

The former Hydration / Soul / Context paths were retracted; their last real callers had moved out months earlier and the prompt-side code was unreachable.


Prompt Modes

ModeBehavior
Full (default)All 36 layers participate
CompactExcludes 15 heavy Stable layers; keeps Dynamic facts per request
MinimalOnly the configured response language + soul + curated memory

Scaffold-Byte Ratchet

prompt_contract::scaffold_bytes_ratchet measures the always-on prompt scaffold across all 5 paradigms and enforces SCAFFOLD_CEILING_BYTES = 5_913 (2026-07-26, WebRich worst case). The ceiling is the max across paradigms rather than a single chosen one because no paradigm dominates:

Background  4,904 B  (default daemon paradigm)
CLI         ~5,140 B
Messaging   ~5,200 B
WebRich     5,913 B   ← ceiling (Background-only + MultiStepConductLayer + DoctorRepairHintLayer)
Embedded    ~5,000 B

The byte budget is measured by aleph-server prompt-size --path cached --paradigm <paradigm> against a production_shaped LayerInput that fills the four always-present fields (runtime_context, approval_tier, session_mode, sandbox_summary) so the ratchet's view matches the bytes the gateway actually sends.


Session Mode

chat / work / code is a user-selected static partition of the tool presentation surface, orthogonal to permissions (approvals stay with the exec tier). Each mode adds one cache-stable prompt line at OperatingEnvelopeLayer so a mode flip moves the byte from the cached prefix into the Dynamic suffix.


UnifiedMessage and ProviderDelta

UnifiedMessage is the LLM-agnostic intermediate message representation; ProviderDelta is the streaming output (TextDelta / ThinkingDelta / ToolCallDelta). A single MessageAssembler reducer owns the assembled message and strips inline <think> blocks live. The harness bridge (src/harness/agent/think.rs) wraps a borrowed HarnessCallback into a DeltaSink so live token deltas reach the gateway callback as the provider streams them.


tool_search Meta-Tool

26.7.7+ progressive tool disclosure: the request carries a static core tool set + the full tool catalog name list, with tool_search as a meta-tool for on-demand schema loading. The model drives discovery 100%; the harness does no message-content-based tool filtering (R10 "5 don'ts" #2). The static partition also lives in src/tools/scoped/ (allowlist / permission Deny / health-gate retain chains), which is in the tool layer, not the harness.


R10's Three Ratchets

TestFileGuards
reachable_layerssrc/thinker/prompt_contract.rsEvery registered layer either contributes under some paradigm or is on CONDITIONALLY_SILENT with the session content that wakes it
scaffold_bytes_ratchetsrc/thinker/prompt_contract.rsAlways-on prompt ≤ SCAFFOLD_CEILING_BYTES across all paradigms
no_sentence_is_stated_twicesrc/thinker/prompt_contract.rsCross-layer sentence-level duplication is forbidden
no_environment_fact_is_stated_twicesrc/thinker/prompt_contract.rsThe same environment fact (OS, cwd, time, …) must not be stated by two layers

aleph-server prompt-size reports the actual byte count and per-layer breakdown for any paradigm.


Thinking Levels

pub enum ThinkingLevel {
    Off,        // No extended thinking
    Minimal,    // budget_tokens: 1024
    Low,        // budget_tokens: 2048
    Medium,     // budget_tokens: 4096 (default)
    High,       // budget_tokens: 8192
    XHigh,      // budget_tokens: 16384
}

The level travels on FlowRequest::think_level and is forwarded into HarnessRunner::run, which puts it on HarnessDeps so every RequestPayload carries it. Providers that lack native extended thinking degrade gracefully — OpenAI's o-series path takes the level natively, Gemini uses a thinkingPreface prompt nudge.


Streaming Architecture

LLM Response Stream


┌─────────────────────────────────────────┐
│ Provider (HTTP / native SDK)            │
│   • Stream chunks → ProviderDelta       │
│   • TextDelta  → live preview           │
│   • ThinkingDelta → reasoning channel   │
│   • ToolCallDelta → fold in assembler   │
└────────────────────┬────────────────────┘


           MessageAssembler (single reducer)
             • Owns the in-progress message
             • Strips inline `<think>` live


       AssistantMessage (SessionEvent)

The harness CallbackSink bridges a borrowed HarnessCallback into the provider layer's DeltaSink; text and thinking deltas are forwarded for live preview, tool-call and bookkeeping deltas are folded by the provider's DeltaCollector and surface through the assembled ProviderResponse.


See Also

  • Harness — Think→Act loop
  • Dispatcher — Flow dispatch + harness runner
  • Memory — Memory envelope + recall-message injection

On this page