Aleph
Concepts

Agent Components

Module map of the Think→Act loop — where the 12 Harness modules live, how they cooperate, and why there is no monolithic `components` directory.

Aleph's Think→Act loop is not an "event-handler bus with 6 typed handlers". That mental model does not exist in the codebase — there is no src/components/ directory. Each subsystem owns its own crate module; they cooperate through the AgentHarness boundary, not through a shared event bus. The split is deliberate, rooted in the harness-dissolution work (2026-04 to 2026-07), and encoded as R3 / R10 / R11 in the design philosophy.

#ModuleAleph homeNotes
1Orchestration Loopsrc/harness/薄核心 — 12 文件/棘轮行数预算
2Toolssrc/tools/ + src/builtin_tools/吸收了原 harness 的 exec_context
3Memorysrc/memory/不动(最干净的一块)
4Context Managementsrc/context/{budget,compact}/5 处合并到 1 处
5Prompt Assemblysrc/thinker/历史名保留,不强行改名
6Tool Callingsrc/tools/calling/ + src/providers/bridge.rs
7State & Checkpointingsrc/session/SessionEventStore + replay() 已经是事件溯源框架
8Error HandlingHarnessError + 各域 typed error跨模块,无单一容身处
9Guardrailssrc/{security,sandbox,approval,pii}/4 域分立,不强搞 facade
10Verification & Feedbacksrc/verification/新建,吸收 stop_hooks
11Subagent Orchestrationsrc/{agents,teams,orchestrator,group_chat}/4 域职责正交,不合并
12Initialization & Envsrc/init_unified/ + src/bin/aleph-server/commands/start/docs/reference/BOOT_ASSEMBLY.md

The Thin Core — src/harness/

The orchestration loop directory is bounded to 12 files and a ratchet-tested line ceiling (src/harness/CLAUDE.md, src/harness/tests/budget.rs). Every file beyond the 12 must answer a 3-question gate before it lands.

FileResponsibility
mod.rsRe-exports AgentHarness
agent.rsAgentHarness struct + top-level run() + stalled-turn grace slot
deps.rsHarnessDeps DI container + StallConfig / StallTracker
trait_def.rsHarnessError, TurnState, TurnStep
callback.rsHarnessCallback + NoopHarnessCallback
chain_context.rsSubagent call-chain position relay
trace.rsLoop LoopTraceEvent collection
trace_sink.rsTraceSink output abstraction
agent/think.rsThink: LLM call + guards + verifier pass
agent/act.rsAct: tool execution + resource-domain parallel scheduling
agent/guardrails.rsInput/output/tool-call guardrail mounts
agent/prompt.rsPer-turn message assembly

The harness does not own: class of intent, tool relevance scoring, completion judgement, content moderation, error-recovery strategy, or provider failover (that's wrapped inside providers::FailoverProvider so the loop sees one provider and never knows failover exists). Those responsibilities are the LLM's, not the loop's.

Loop state

Two-type state machine in src/harness/trait_def.rs:

#[must_use]
pub enum TurnState { Continue, Done }

#[must_use]
pub struct TurnStep {
    pub state: TurnState,
    pub executed: usize,
    pub vetoed: bool,
    pub split_child: Option<SessionId>,
}

TurnState has two variants, not five. The richer Restart | Act | Finalize split shown in some drafts was collapsed when the restart arm turned out to be reachable from one site only — represented now as an HarnessError early-return, not a separate state.

Prompt Assembly — src/thinker/

Layered system-prompt builder. See Prompt System for the layer pipeline, the two entry points (Basic / Cached), and the cacheable/transient boundary. The harness's agent/prompt.rs calls PromptBuilder::build_system_prompt_cached_with_mode exactly once per turn.

Tools — src/tools/ + src/builtin_tools/

ToolService is the registry and the dispatch entry point. Three subsystems:

  • Scoping (src/tools/scoped/) — runtime allowlist / deny / health filtering per request. Mechanical retain operations, not LLM-influenced.
  • Resource claims (src/tools/resource_claim.rs) — each tool self-declares one of Shared, Exclusive{Global}, Exclusive{Paths} so the Act phase knows what can run in parallel. Inter-claim serial, intra-claim concurrent.
  • Result spill (src/tools/{turn_budget, result_store}.rs) — Layer 3 spill-to-disk for oversize tool outputs; session-scoped ToolResultStore.
  • Calling (src/tools/calling/) — schema-constrained tool-call parsing/serialization; the harness's act consumes NativeToolCall from the provider adapter.

Concurrent tool batching happens inside the harness's act step, not in a separate dispatcher; see parallel_tool_concurrency on HarnessDeps.

Session — src/session/

Append-only event log. The frame is event sourcing, not snapshotting — SqliteEventStore plus InProcessActorSessionService, building replay via get_events(session, Some(seq), None). The harness reads from: watermark+1 where last_prompt_seq records the seq of the last event covered by the current turn's prompt. There is no separate checkpoint subsystem: Git-style trait sketches for checkpointing have been retracted as dead abstractions (P5 retraction).

Memory — src/memory/

Three storage kinds, kept separate:

  • Facts DB / vector searchsrc/memory/ core
  • Curated memory envelope — pre-rendered <CuratedMemory> block (CuratedMemoryLayer @60, Stable), threaded into every LayerInput so the prompt-cache stays warm
  • Recall message — per-query semantic recall travels as a transient trailing user message (HarnessDeps::recall_context), never as part of the system prompt. Putting it in the system prompt re-keyed the conversation-prefix cache every run; placing it at the message tail costs only itself.

Context Management — src/context/

Two complementary subsystems:

  • src/context/budget/ContextBudget, PreflightPipeline, pressure::*. Surfaces a LoopDirective (CompactToFit, SplitSession, …) the harness carries out mechanically.
  • src/context/compact/ContextCompactor, directive.rs (dispatcher), rescue.rs (reactive rescue host), pipeline.rs, plan_carry.rs. Reactive rescue is bounded by MAX_REACTIVE_COMPACT_ATTEMPTS in context/compact/rescue.rs, and the rescue slot in the harness reads that constant via a 52-line adapter — rg "crate::harness" src/context/ returns empty (seam lives in context; state lives in harness).

Orchestrator — src/orchestrator/

Builds the AgentHarness for each run:

  • resolver.rsRoutingOverrides, MAX_FLOW_DEPTH
  • flow_spec.rsFlowSpec, FlowOverrides, SessionStrategy
  • flow_registry.rs — registry of FlowSpecs per agent_id
  • dispatch.rsOrchestrator, FlowHandle, FlowOutcome, FlowRequest, FlowStreamEvent, HarnessRunner, TerminateReason
  • harness_bridge/AgentHarnessRunner (the bridge from flow dispatch down to harness construction)
  • deps_builder/ — helpers that assemble HarnessDeps
  • sandbox_factory.rs — sandbox-per-flow policy

The orchestrator is a DI assembler, not a coordinator. It owns one decision per run (which AgentDef + which FlowSpec to honour); after that, control flows into the harness.

Subagent Surfaces — Kept Separate on Purpose

Originally planned as a single SubagentOrchestrator trait with Fork / Handoff / Graph modes. Withdrawn (P5 retraction) as a zero-consumer abstraction — there were no call sites asking for that trait. Today the four surfaces coexist orthogonally:

  • src/agents/SubagentTool, AgentRuntime, registry, subagent spawner
  • src/teams/ — team coordinator
  • src/orchestrator/ — multi-agent dispatch (already covered above)
  • src/group_chat/ — group chat shared-nothing broadcast

Each owns a distinct responsibility. There is no facade.

Verification & Feedback — src/verification/

VerifierChain is consulted between Think and Act every iteration (src/harness/agent/think.rs::run_verifiers). Composition:

  • StopHookVerifier — preserves pre-Stage 6a "veto before Done" semantics
  • ToolLoopVerifier — detects tool_use death-loops

A Veto forces one extra Continue so the model can react; it's not a hard stop. There is also ModelRobustnessProfile per model id, threaded via HarnessDeps.robustness_profile, so verifier sensitivity scales with model behaviour.

Guardrails — 4-Domain Split

src/{security, sandbox, approval, pii}/ — four separate domains rather than one Guardrail facade. The harness mounts each at the appropriate seam:

  • Input guardrailsagent/guardrails.rs mounts the GuardrailRegistry::screen_session_input; mod runs on historical messages too (a Block decision is rewritten as redaction, so immutable history cannot brick a session).
  • Output guardrailsagent/think.rs, after streaming finalize but before commit; max-output-tokens resumption concatenates segments.
  • Tool-call guardrailsagent/guardrails.rs::apply_tool_call_guardrail, outcomes Pass | Sanitize(Value) | Block. Applied sequentially in act's PASS 0, before the parallel join_all (Pi-style validate-then-execute).

safety.rs, truncation_recovery.rs, streaming_bridge.rs are also part of the harness's safety surface but live inside src/harness/agent/.

Initialisation & Environment — src/init_unified/

See Boot Assembly. Five-phase setup runs at InitializationCoordinator (src/init_unified/coordinator.rs, re-exported at src/lib.rs:99); runtime boot wired through src/bin/aleph-server/commands/start/ (5 sub-builders, ~6,194 LOC total).

What does NOT exist (and why this list is short)

The legacy src/components/{intent_analyzer,task_planner,tool_executor, loop_controller,session_recorder,session_compactor}.rs directory and its five (or six) EventHandler-style subscribers were dissolved as part of the 2026-04 → 2026-07 harness dissolution wave:

  • Intent classification is the LLM's job (R8), not a rule-based analyser.
  • Task DAG planning in the loop breaks ReAct (R8 / R10 #1).
  • Tool execution lives at src/tools/scoped/dispatch.rs::execute_inner — the loop only knows act(call).
  • Loop protection (max_iterations, consecutive_failure_cap, per-tool timeouts, verifier veto) is configuration on HarnessDeps, not a component.
  • Session recording is src/session/SqliteEventStore, an event log — the same thing with a different name.

Deleting the src/components/ facade removed ~3,000 LOC of dead subscribers without changing any external behaviour.

Code Locations

  • src/harness/ — Think→Act driver (12 files; budget ratcheted)
  • src/thinker/ — PromptBuilder + PromptPipeline layers
  • src/tools/, src/builtin_tools/ToolService, scoping, resource claims
  • src/session/SessionService, SqliteEventStore
  • src/memory/ — facts DB, vector search, recall message assembly
  • src/context/{budget,compact}/ — pressure sensor + compactor + reactive rescue
  • src/verification/VerifierChain, StopHookVerifier, ToolLoopVerifier
  • src/{security,sandbox,approval,pii}/ — guardrail domains
  • src/orchestrator/Orchestrator, AgentHarnessRunner, HarnessDeps builders
  • src/agents/SubagentTool, AgentRuntime, registry
  • src/teams/, src/group_chat/ — multi-agent surfaces
  • src/init_unified/InitializationCoordinator + boot-time wiring

See Also

On this page