Dependency graph (8-layer DAG)
A senior engineer evaluating adoption spends 5 minutes on the README, 10 minutes on examples, then wants to see the dependency graph + layer separation. This page is that map.
The 8 layers
Section titled βThe 8 layersβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 8 β Public Barrel (src/index.ts) ββ What consumers `import` from. Re-exports curated subset. ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β²ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 7 β Compositions (Sequence, Parallel, Conditional, Loop) ββ Multi-runner orchestration. Each composition IS a runner. ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β²ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 6 β Primitives (Agent, LLMCall) ββ Single-runner units. Agent = ReAct loop. LLMCall = one shot.ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β²ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 5 β Context Engineering (InjectionEngine) ββ Skill, Steering, Instruction, Fact, Memory, RAG β all ββ reduce to {trigger, slot, content} via one engine. ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β²ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 4 β Memory subsystem (defineMemory + pipelines) ββ 4 types Γ 7 strategies Γ N stores. Per-tenant isolation. ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β²ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 3 β Adapters (LLM providers, MemoryStore impls, MCP) ββ Outer ring (Hexagonal). Hand-written shims around vendor ββ SDKs. Lazy-required peer-deps. ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β²ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 2 β Event taxonomy + dispatcher ββ 47 typed events Γ 13 domains. EventDispatcher. ββ AgentfootprintEvent discriminated union. ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β²ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Layer 1 β Substrate (footprintjs) ββ Flowchart execution engine. DFS traversal owns the loop. ββ This is what makes every layer above's observability ββ automatic. ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββReading this diagram
Section titled βReading this diagramβ- Each layer depends only on layers below it. No cycles. A layer-5 file imports from layers 1β4 only.
- Consumers can reach any layer directly. A power user who wants to build a custom recorder reaches Layer 2 directly via the public barrel. A power user who wants to wrap a new LLM provider implements at Layer 3 via the published interfaces.
- The substrate at Layer 1 is what makes everything above it composable. Because footprintjs owns the traversal, every layer above gets typed-event observability for free.
Why this shape
Section titled βWhy this shapeβTwo properties make the architecture worth describing:
1. The substrate is shared
Section titled β1. The substrate is sharedβfootprintjs at Layer 1 is the same engine running underneath Agent, Sequence, Parallel, Conditional, Loop. Every runner traverses a flowchart; every traversal emits typed events; every event flows through the same dispatcher. This is why you can agent.on('agentfootprint.composition.iteration_start') on a Loop β itβs just another flowchart traversal under the hood.
2. Adapters are isolated at the outer ring
Section titled β2. Adapters are isolated at the outer ringβLayer 3 β providers, stores, MCP β is the only layer that talks to the outside world. The whole rest of the library has zero dependency on @anthropic-ai/sdk, openai, ioredis, @modelcontextprotocol/sdk, or any other vendor SDK. Adapters are lazy-required at first call; consumers who use only mock() + InMemoryStore install zero peer-deps. This is the Hexagonal / Ports-and-Adapters discipline (Cockburn 2005) applied honestly.
Subpath exports = subset boundaries
Section titled βSubpath exports = subset boundariesβThe package.json exports field publishes subpath imports per layer:
| Subpath | Layer | What it exposes |
|---|---|---|
agentfootprint (default) | 8 | The full curated barrel β 90% of consumers use only this |
agentfootprint/memory | 4 | defineMemory, MEMORY_TYPES, MEMORY_STRATEGIES, InMemoryStore |
agentfootprint/llm-providers | 3 | LLM provider adapters (canonical v2.5+) |
agentfootprint/memory-providers | 3 | Memory store adapters β RedisStore, AgentCoreStore, future stores (canonical v2.5+) |
agentfootprint/tool-providers | 3 | Tool dispatch + sources β staticTools, gatedTools, skillScopedTools, mcpClient |
agentfootprint/security | 3 | Cross-cutting authorization β PermissionPolicy |
agentfootprint/providers | 3 | Legacy alias for llm-providers (still works through v2.x; removed v3.0) |
agentfootprint/memory-redis | 3 | Legacy per-adapter alias (still works through v2.x; removed v3.0) |
agentfootprint/memory-agentcore | 3 | Legacy per-adapter alias (still works through v2.x; removed v3.0) |
agentfootprint/observe | 2 | Event taxonomy + dispatcher types |
agentfootprint/resilience | 3 | withRetry, withFallback, resilientProvider |
agentfootprint/stream | (cross-cutting) | SSE bridge for streaming agent events to HTTP |
agentfootprint/injection-engine | 5 | Injection, InjectionTrigger, evaluateInjections |
Each subpath maps to a single layer (or in some cases a single layerβs slice). Tree-shaking picks up only whatβs used.
The Dynamic ReAct loop β runtime view of the same architecture
Section titled βThe Dynamic ReAct loop β runtime view of the same architectureβThe 8-layer DAG describes the static dependency shape β what imports what. The RUNTIME shape is the agentβs flowchart loop, which IS the substrate (Layer 1, footprintjs) executing through the layers above it. The loop is what makes the architecture EXPRESSIVE.
ββββββββββββββββββββββββββββ β agent.run({message}) β ββββββββββββ¬ββββββββββββββββ βΌ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β iteration N starts here every time the loop iterates β β β β ββββββββββββββββββββ β β β INJECTION_ENGINE β evaluates EVERY trigger this iteration β β ββββββββββββ¬ββββββββ (always / rule / on-tool-return / β β βΌ llm-activated). Outputs activeInjections β β ββββββββββββββββββββ β β β SYSTEM_PROMPT β composes system slot from active set β β ββββββββββββ¬ββββββββ β β βΌ β β ββββββββββββββββββββ β β β MESSAGES β composes messages slot (+ history) β β ββββββββββββ¬ββββββββ β β βΌ β β ββββββββββββββββββββ β β β TOOLS β composes tools slot (per-skill gating, β β ββββββββββββ¬ββββββββ permission filter β Block A1+A2+A5) β β βΌ β β ββββββββββββββββββββ β β β CallLLM β the LLM sees a freshly-composed β β ββββββββββββ¬ββββββββ prompt + tool list every iteration β β βΌ β β ββββββββββββββββββββ β β β Route decider β tool calls? β execute β loop back β β β βββββββ¬βββββββββββββ no tool calls? β final β done β β β β βββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β if tool calls executed: βββΊ append result to history β loopTo(INJECTION_ENGINE) β RE-RUNS THE WHOLE PIPELINE TOP-DOWN (this is what makes Dynamic ReAct work)Why this matters architecturally: every iteration re-evaluates triggers against the FRESHEST context. Tool results from iteration N reshape the system prompt, tool list, and active skills for iteration N+1. The frameworkβs typed-event stream + replayable traces are the OBSERVABILITY consequence; per-iteration recomposition is the EXPRESSIVE consequence.
This is what we mean by βthe framework owns the loop.β Itβs not just that we own the iteration count β we own the slot composition pipeline that runs THROUGH each iteration. Without that, βcontext engineeringβ collapses to βstatic prompt assemblyβ and the frameworkβs distinctness disappears.
See Dynamic ReAct guide for the full mechanics + use cases.
What this enables
Section titled βWhat this enablesβ- Power users build at the layer they need. Want a custom Memory store? Implement Layer 3βs
MemoryStoreinterface; everything above unchanged. Want a new pattern? Compose Layer 7 over existing Layer 6 primitives. - Plugin authors build at the boundary. A βSkills inspectorβ library subscribes to
agentfootprint.skill.*events at Layer 2; doesnβt need to touch the InjectionEngine. - The libraryβs own internals follow the same rule. No layer-7 file imports a layer-6 fileβs private internals; layer-6 files import only published Layer-5 + below interfaces.
Anti-cycles enforced by tooling
Section titled βAnti-cycles enforced by toolingβscripts/check-dup-types.mjs runs in CI to detect circular type imports across layers. The release script (Gate 2.5) blocks releases that introduce cycles.
Related principles
Section titled βRelated principlesβ- GoF Adapter pattern β every Layer-3 file is one
- GoF Decorator pattern β
withRetry/withFallback(Layer 3, on top of LLMProvider) - Hexagonal Architecture (Cockburn 2005) β Layers 1β2 are the inner rings; Layer 3 is the outer ring
- footprintjs flowchart pattern β the substrate that makes the typed-event observability automatic
Next steps
Section titled βNext stepsβ- Manifesto β the frameworkβs perspective on what it is + isnβt
- Custom provider β implement at Layer 3
- footprintjs β Layer 1 substrate, if you want to build at this depth