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.
| # | Module | Aleph home | Notes |
|---|---|---|---|
| 1 | Orchestration Loop | src/harness/ | 薄核心 — 12 文件/棘轮行数预算 |
| 2 | Tools | src/tools/ + src/builtin_tools/ | 吸收了原 harness 的 exec_context |
| 3 | Memory | src/memory/ | 不动(最干净的一块) |
| 4 | Context Management | src/context/{budget,compact}/ | 5 处合并到 1 处 |
| 5 | Prompt Assembly | src/thinker/ | 历史名保留,不强行改名 |
| 6 | Tool Calling | src/tools/calling/ + src/providers/bridge.rs | |
| 7 | State & Checkpointing | src/session/ | SessionEventStore + replay() 已经是事件溯源框架 |
| 8 | Error Handling | HarnessError + 各域 typed error | 跨模块,无单一容身处 |
| 9 | Guardrails | src/{security,sandbox,approval,pii}/ | 4 域分立,不强搞 facade |
| 10 | Verification & Feedback | src/verification/ | 新建,吸收 stop_hooks |
| 11 | Subagent Orchestration | src/{agents,teams,orchestrator,group_chat}/ | 4 域职责正交,不合并 |
| 12 | Initialization & Env | src/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.
| File | Responsibility |
|---|---|
mod.rs | Re-exports AgentHarness |
agent.rs | AgentHarness struct + top-level run() + stalled-turn grace slot |
deps.rs | HarnessDeps DI container + StallConfig / StallTracker |
trait_def.rs | HarnessError, TurnState, TurnStep |
callback.rs | HarnessCallback + NoopHarnessCallback |
chain_context.rs | Subagent call-chain position relay |
trace.rs | Loop LoopTraceEvent collection |
trace_sink.rs | TraceSink output abstraction |
agent/think.rs | Think: LLM call + guards + verifier pass |
agent/act.rs | Act: tool execution + resource-domain parallel scheduling |
agent/guardrails.rs | Input/output/tool-call guardrail mounts |
agent/prompt.rs | Per-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. Mechanicalretainoperations, not LLM-influenced. - Resource claims (
src/tools/resource_claim.rs) — each tool self-declares one ofShared,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-scopedToolResultStore. - Calling (
src/tools/calling/) — schema-constrained tool-call parsing/serialization; the harness'sactconsumesNativeToolCallfrom 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 search —
src/memory/core - Curated memory envelope — pre-rendered
<CuratedMemory>block (CuratedMemoryLayer@60, Stable), threaded into everyLayerInputso 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 aLoopDirective(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 byMAX_REACTIVE_COMPACT_ATTEMPTSincontext/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.rs—RoutingOverrides,MAX_FLOW_DEPTHflow_spec.rs—FlowSpec,FlowOverrides,SessionStrategyflow_registry.rs— registry ofFlowSpecs peragent_iddispatch.rs—Orchestrator,FlowHandle,FlowOutcome,FlowRequest,FlowStreamEvent,HarnessRunner,TerminateReasonharness_bridge/—AgentHarnessRunner(the bridge from flow dispatch down to harness construction)deps_builder/— helpers that assembleHarnessDepssandbox_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 spawnersrc/teams/— team coordinatorsrc/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" semanticsToolLoopVerifier— detectstool_usedeath-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 guardrails —
agent/guardrails.rsmounts theGuardrailRegistry::screen_session_input; mod runs on historical messages too (aBlockdecision is rewritten as redaction, so immutable history cannot brick a session). - Output guardrails —
agent/think.rs, after streaming finalize but before commit; max-output-tokens resumption concatenates segments. - Tool-call guardrails —
agent/guardrails.rs::apply_tool_call_guardrail, outcomesPass | Sanitize(Value) | Block. Applied sequentially inact's PASS 0, before the paralleljoin_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 knowsact(call). - Loop protection (
max_iterations,consecutive_failure_cap, per-tool timeouts, verifier veto) is configuration onHarnessDeps, 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 layerssrc/tools/,src/builtin_tools/—ToolService, scoping, resource claimssrc/session/—SessionService,SqliteEventStoresrc/memory/— facts DB, vector search, recall message assemblysrc/context/{budget,compact}/— pressure sensor + compactor + reactive rescuesrc/verification/—VerifierChain,StopHookVerifier,ToolLoopVerifiersrc/{security,sandbox,approval,pii}/— guardrail domainssrc/orchestrator/—Orchestrator,AgentHarnessRunner,HarnessDepsbuilderssrc/agents/—SubagentTool,AgentRuntime, registrysrc/teams/,src/group_chat/— multi-agent surfacessrc/init_unified/—InitializationCoordinator+ boot-time wiring
See Also
- Agent Runtime — runtime path end-to-end
- Harness Architecture — thin-harness philosophy
- Prompt System — layer pipeline + caching
- Tool System — resource claims
- Boot Assembly — module wiring at startup
- Redlines R3 / R10 / R11 — the harness boundary