agentfootprint 9.37.0 → 9.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/ai-instructions/claude-code/SKILL.md +177 -353
  2. package/dist/adapters/code/local.js +15 -0
  3. package/dist/adapters/code/local.js.map +1 -1
  4. package/dist/artifacts/placement.js.map +1 -1
  5. package/dist/artifacts/streaming.js +30 -1
  6. package/dist/artifacts/streaming.js.map +1 -1
  7. package/dist/artifacts/types.js.map +1 -1
  8. package/dist/artifacts/wants.js +62 -5
  9. package/dist/artifacts/wants.js.map +1 -1
  10. package/dist/core/agent/stages/toolCalls.js +30 -4
  11. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  12. package/dist/core/codeRunnerTool.js +48 -2
  13. package/dist/core/codeRunnerTool.js.map +1 -1
  14. package/dist/core/tools.js.map +1 -1
  15. package/dist/doors/skill-graph.js +55 -6
  16. package/dist/doors/skill-graph.js.map +1 -1
  17. package/dist/esm/adapters/code/local.d.ts +15 -0
  18. package/dist/esm/adapters/code/local.js +15 -0
  19. package/dist/esm/adapters/code/local.js.map +1 -1
  20. package/dist/esm/adapters/types.d.ts +14 -0
  21. package/dist/esm/artifacts/placement.d.ts +10 -0
  22. package/dist/esm/artifacts/placement.js.map +1 -1
  23. package/dist/esm/artifacts/streaming.d.ts +30 -1
  24. package/dist/esm/artifacts/streaming.js +30 -1
  25. package/dist/esm/artifacts/streaming.js.map +1 -1
  26. package/dist/esm/artifacts/types.d.ts +7 -1
  27. package/dist/esm/artifacts/types.js.map +1 -1
  28. package/dist/esm/artifacts/wants.d.ts +11 -3
  29. package/dist/esm/artifacts/wants.js +62 -5
  30. package/dist/esm/artifacts/wants.js.map +1 -1
  31. package/dist/esm/core/agent/stages/toolCalls.js +30 -4
  32. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  33. package/dist/esm/core/codeRunnerTool.js +48 -2
  34. package/dist/esm/core/codeRunnerTool.js.map +1 -1
  35. package/dist/esm/core/tools.d.ts +7 -0
  36. package/dist/esm/core/tools.js.map +1 -1
  37. package/dist/esm/doors/skill-graph.d.ts +55 -6
  38. package/dist/esm/doors/skill-graph.js +55 -6
  39. package/dist/esm/doors/skill-graph.js.map +1 -1
  40. package/dist/esm/lib/injection-engine/skillGraph.d.ts +7 -1
  41. package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
  42. package/dist/esm/lib/injection-engine/types.d.ts +17 -0
  43. package/dist/esm/lib/injection-engine/types.js.map +1 -1
  44. package/dist/lib/injection-engine/skillGraph.js.map +1 -1
  45. package/dist/lib/injection-engine/types.js.map +1 -1
  46. package/dist/types/adapters/code/local.d.ts +15 -0
  47. package/dist/types/adapters/code/local.d.ts.map +1 -1
  48. package/dist/types/adapters/types.d.ts +14 -0
  49. package/dist/types/adapters/types.d.ts.map +1 -1
  50. package/dist/types/artifacts/placement.d.ts +10 -0
  51. package/dist/types/artifacts/placement.d.ts.map +1 -1
  52. package/dist/types/artifacts/streaming.d.ts +30 -1
  53. package/dist/types/artifacts/streaming.d.ts.map +1 -1
  54. package/dist/types/artifacts/types.d.ts +7 -1
  55. package/dist/types/artifacts/types.d.ts.map +1 -1
  56. package/dist/types/artifacts/wants.d.ts +11 -3
  57. package/dist/types/artifacts/wants.d.ts.map +1 -1
  58. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  59. package/dist/types/core/tools.d.ts +7 -0
  60. package/dist/types/core/tools.d.ts.map +1 -1
  61. package/dist/types/doors/skill-graph.d.ts +55 -6
  62. package/dist/types/doors/skill-graph.d.ts.map +1 -1
  63. package/dist/types/lib/injection-engine/skillGraph.d.ts +7 -1
  64. package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
  65. package/dist/types/lib/injection-engine/types.d.ts +17 -0
  66. package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
  67. package/package.json +1 -1
@@ -1,445 +1,269 @@
1
1
  ---
2
2
  name: agentfootprint
3
- description: Use when building Generative AI applications with agentfootprint — LLM calls, ReAct agents, multi-agent compositions (Sequence/Parallel/Conditional/Loop), context engineering (Skill/Steering/Instruction/Fact), memory (4 types × 7 strategies including Causal snapshots), tools, providers, observability, pause/resume, and patterns (Reflexion/ToT/Debate/MapReduce/Swarm). Also use when someone asks how agentfootprint works.
3
+ description: Use when building AI agents with agentfootprint — LLMCall, Agent, skills, RAG, memory, control flow, Swarm concepts, mock/anthropic/openai/ollama providers, tools, recorders, resilience, and streaming. Also use when someone asks how agentfootprint works or wants to understand the framework.
4
4
  ---
5
5
 
6
- # agentfootprint — Skill
6
+ # agentfootprint — The Explainable Agent Framework
7
7
 
8
- Building Generative AI applications is mostly **context engineering** deciding what content lands in which slot of the LLM call, when, and why. agentfootprint exposes this discipline through 2 primitives + 3 compositions + 1 unifying injection primitive + 1 memory factory.
8
+ agentfootprint structures AI agents as composable flowcharts, so every injection, read, write, decision and tool call becomes connected evidence as the run happens. Every concept takes an `LLMProvider` swap `mock({...})` for `anthropic({...})` with zero code changes.
9
9
 
10
- Built on [footprintjs](https://github.com/footprintjs/footPrint) — the flowchart pattern for backend code. Every agent run produces a causal trace through the same DFS traversal, so observability is "free" (no instrumentation, no post-processing).
10
+ **Core principles:**
11
+ - Adapter-swap testing ($0 test runs, deterministic assertions)
12
+ - The ladder: `mock` → `ollama` (free, local, real model) → a paid provider
13
+ - Declare context (facts, steering, skills); the framework decides WHEN it fires and WHICH slot it lands in
14
+ - Collect during traversal, never post-process (inherited from footprintjs)
11
15
 
12
- ## The 6-layer mental model
13
-
14
- ```
15
- 2 primitives : LLMCall · Agent (= ReAct)
16
- 3 compositions+Loop : Sequence · Parallel · Conditional · Loop
17
- N patterns : ReAct · Reflexion · ToT · MapReduce · Debate · Swarm (RECIPES, not classes)
18
- Context engineering : defineSkill · defineSteering · defineInstruction · defineFact
19
- Memory : defineMemory({type, strategy, store}) — 4 types × 7 strategies
20
- Production features : pause/resume · cost · permissions · observability · events
16
+ ```bash
17
+ npm install agentfootprint footprintjs
21
18
  ```
22
19
 
23
- ## Three slots × six flavors
20
+ ## Read this first what does NOT exist
24
21
 
25
- Every LLM call has three slots, every "agent feature" is content flowing into one of them:
22
+ These are not hypothetical. A capable author, working from a correct mental model of
23
+ this library, invented all four in one document. Treat them as the things you are
24
+ most likely to get wrong here.
26
25
 
27
- | LLM API field | What goes here |
26
+ | You will reach for | The reality |
28
27
  |---|---|
29
- | `system` prompt | Steering · Instruction · Skill body · Fact data · formatted memory |
30
- | `messages` array | The conversationuser turns, assistant turns, tool results. Assembled from the conversation itself, never injected into |
31
- | `tools` array | Tool schemas (registered + Skill-attached) |
28
+ | `startRun(...)` | **No such function.** The door is `agent.run(input, options?)`, where `AgentInput = { message: string; identity?; continueFrom? }` and `AgentOutput = string`. `run()` returns `AgentOutput \| RunnerPauseOutcome` — a run paused for a human returns a checkpoint; discriminate with `isPaused(result)`. |
29
+ | `RunStep` as skill/route history | **`RunStep` is real and it is something else** the footprintjs flowchart TOPOLOGY slider, exported from `agentfootprint/observe`. Its `kind` is `'sequential' \| 'fork' \| 'merge' \| 'decide' \| 'iteration' \| 'iteration-exit' \| 'react'`. Nothing in it concerns skills. Importing it succeeds, which is exactly why it is dangerous. For route history use `routeRecorder()` from the same door. |
30
+ | the LLM classifier as routing "tier 3" | **It is a tier-2 strategy.** Tier 1 = declared start rules. Tier 2 = the configured scorer — `llmClassifier(provider)` OR `keywordScorer()` OR `embeddingScorer(e)` OR the entry scorer; near-ties fall through rather than argmax. Tier 3 = a menu the model resolves in-band through `read_skill`'s own description, reached only when tier 2 was NOT decisive. |
31
+ | a skill's tools being gated to that skill automatically | **They are not, by default.** `defineSkill({ tools })` puts them in the agent's static tool list at build time — visible from iteration 1 whether the skill ever activates or not. Ask for the gate: `.toolsFromActiveSkill()` (agent-wide), `skillGraph({ scopeTools: true })` (graph-wide), or `autoActivate: 'currentSkill'` (per skill). `.tree()` leaves are the one shape scoped by default. |
32
32
 
33
- The flavors are intent markers all reduce to one `Injection` primitive:
33
+ Two more absences: there is **no runtime force-stop governor** (`routeRecorder().getTrips()`
34
+ only *labels* a spinning run; `maxIterations` is the hard stop), and there is **no
35
+ automatic re-delivery of an ageing skill body** (`refreshPolicy` is stored and never
36
+ read on any version — use `surfaceMode: 'both'`).
34
37
 
35
- | Flavor | Trigger | Slots |
36
- |---|---|---|
37
- | **Skill** | LLM-activated (`read_skill`) | system-prompt + tools |
38
- | **Steering** | Always-on | system-prompt |
39
- | **Instruction** | Predicate (`activeWhen` / `on-tool-return`) | system-prompt |
40
- | **Fact** | Always-on (data) | system-prompt |
41
-
42
- ## Public API
43
-
44
- ```typescript
45
- import {
46
- // Primitives + compositions
47
- Agent, LLMCall, defineTool,
48
- Sequence, Parallel, Conditional, Loop,
49
-
50
- // Context engineering — 4 typed factories over one Injection primitive
51
- defineSkill, defineSteering, defineInstruction, defineFact,
52
-
53
- // Memory — one factory, 4 types × 7 strategies
54
- defineMemory,
55
- MEMORY_TYPES, MEMORY_STRATEGIES, MEMORY_TIMING, SNAPSHOT_PROJECTIONS,
56
- InMemoryStore, mockEmbedder,
57
-
58
- // Providers (adapters)
59
- anthropic, openai, bedrock, ollama, mock,
60
-
61
- // Pause / resume / resilience
62
- askHuman, pauseHere, isPaused,
63
- withRetry, withFallback, resilientProvider,
64
- } from 'agentfootprint';
65
- ```
38
+ ## Subpath map 13 doors
66
39
 
67
- **Top-level barrel only.** Don't import from stale subpaths
68
- (`agentfootprint/instructions`, `agentfootprint/observe`,
69
- `agentfootprint/security`, `agentfootprint/explain` — these are v1).
40
+ `agentfootprint` (main barrel: `Agent`, `LLMCall`, `defineTool`, control flow, patterns, `defineRAG`, pause/resume) · `/providers` (`mock`, `anthropic`, `openai`, `bedrock`, `ollama`, `mcpClient`, embedders — every provider, so bundlers never walk the vendor SDKs from the main barrel) · `/context` (`defineSkill`, `defineFact`, `defineSteering`, `defineInstruction`, `skillGraph`, `skillsFromDir`, the scorers) · `/memory` (`defineMemory`, `InMemoryStore`, `mockEmbedder`, the stores) · `/rag` (stores + loaders; `defineRAG` itself is on the main barrel) · `/observe` (recorders, tracing, `RunStep`) · `/resilience` (provider decorators) · `/reliability` (the rules-based fail-fast gate) · `/cache` (prefix-cache strategies; importing it registers them) · `/security` · `/hosting` · `/events` · `/skill-graph` (the routing layer with no framework attached, for a host that is not this agent).
70
41
 
71
- ## Mock-first development (RECOMMENDED)
42
+ ## Core Concepts
72
43
 
73
- Build the entire agent + context engineering + tools + memory + RAG + MCP with in-memory mocks first. Validate logic and patterns end-to-end with $0 API cost. Swap real infrastructure in one boundary at a time after the flow is right.
44
+ ### LLMCall a single LLM call, no tools
74
45
 
75
- | Mock | Production swap |
76
- |---|---|
77
- | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `ollama('<model>')` — a real model, still $0 and no key · then `anthropic()` / `openai()` / `bedrock()` |
78
- | `InMemoryStore` | `RedisStore` (`agentfootprint/memory`) · `AgentCoreStore` (`agentfootprint/memory`) · Dynamo · Postgres · Pinecone (planned) |
79
- | `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
80
- | `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` real server |
81
- | inline `defineTool({ execute: async () => '...' })` | real implementation |
46
+ ```typescript
47
+ import { LLMCall } from 'agentfootprint';
48
+ import { mock } from 'agentfootprint/providers';
82
49
 
83
- When generating starter code, default to the mock surface unless the user explicitly says they have a key / endpoint / store ready.
50
+ const caller = LLMCall.create({ provider: mock({ reply: 'Hello!' }), model: 'mock' }).system('You are helpful.').build();
51
+ const result = await caller.run({ message: 'Hi' });
52
+ ```
84
53
 
85
- ## Hello agent mock-first
54
+ ### Agent — a ReAct agent with tools
86
55
 
87
56
  ```typescript
57
+ import { Agent, defineTool } from 'agentfootprint';
58
+ import { mock } from 'agentfootprint/providers';
59
+
88
60
  const weather = defineTool({
89
- schema: {
90
- name: 'weather',
91
- description: 'Current weather for a city.',
92
- inputSchema: {
93
- type: 'object',
94
- properties: { city: { type: 'string' } },
95
- required: ['city'],
96
- },
97
- },
98
- execute: async (args) => `${(args as { city: string }).city}: 72°F`, // mock data
61
+ name: 'weather',
62
+ description: 'Get current weather for a city.',
63
+ inputSchema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
64
+ execute: async ({ city }: { city: string }) => `${city}: 72°F, sunny`,
99
65
  });
100
66
 
101
- const agent = Agent.create({
102
- provider: mock({ reply: 'San Francisco: 72°F, sunny.' }), // ← no API key
103
- model: 'mock',
104
- maxIterations: 10,
105
- })
106
- .system('You are a helpful weather assistant.')
67
+ const agent = Agent.create({ provider: mock({ reply: 'It is 72°F.' }), model: 'mock' })
68
+ .system('You answer weather questions using the weather tool.')
107
69
  .tool(weather)
70
+ .maxIterations(5)
108
71
  .build();
109
72
 
110
- const result = await agent.run({ message: 'Weather in SF?' });
73
+ const result = await agent.run({ message: 'Weather in Paris?' });
111
74
  ```
112
75
 
113
- When the logic is right, swap to a real provider — one line:
76
+ ### Context facts, steering, skills, and declared routing
114
77
 
115
78
  ```typescript
116
- provider: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }),
117
- model: 'claude-sonnet-4-5-20250929',
79
+ import { defineFact, defineSteering, defineSkill, skillGraph } from 'agentfootprint/context';
80
+
81
+ Agent.create({ provider, model })
82
+ .fact(defineFact({ id: 'user-profile', data: 'Plan: Pro · Customer since 2022' }))
83
+ .steering(defineSteering({ id: 'policy', prompt: 'Never promise a refund before checking.' }))
84
+ .skill(defineSkill({ id: 'refunds', description: 'Refund procedure.', body: '…', tools: [issueRefund] }))
85
+ .build();
118
86
  ```
119
87
 
120
- ## Context engineering
88
+ `defineSkill` bodies load on demand — the model opens one with `read_skill`, or a `skillGraph()` routes to it:
121
89
 
122
90
  ```typescript
123
- // Always-on rule (system-prompt)
124
- const tone = defineSteering({
125
- id: 'tone',
126
- prompt: 'Be friendly and concise.',
127
- });
128
-
129
- // Predicate-gated
130
- const urgent = defineInstruction({
131
- id: 'urgent',
132
- activeWhen: (ctx) => /urgent|asap/i.test(ctx.userMessage),
133
- prompt: 'Prioritize the fastest path to resolution.',
134
- });
135
-
136
- // Dynamic ReAct — fires AFTER a specific tool returned (system slot, that turn only)
137
- const afterRedact = defineInstruction({
138
- id: 'after-redact',
139
- activeWhen: (ctx) => ctx.lastToolResult?.toolName === 'redact_pii',
140
- prompt: 'Use the redacted text only. Do not paraphrase the original.',
141
- });
142
- // `slot: 'messages'` DELIVERS since 7.21.0 — it appends to `scope.history`, so the
143
- // window strategies, the trace and the wire all see it. It requires a `role` (no
144
- // default), and both wire rules can refuse: a role the provider does not carry
145
- // inside `messages` throws at RUN START naming the provider (Anthropic-family
146
- // drops 'system' there, OpenAI-family carries it), and a role that would repeat
147
- // the turn at the end of the window is DEFERRED to the next boundary with a
148
- // reason on `messagesDelivery.deferred`. Practical consequence: inside a
149
- // tool-using loop `role: 'user'` typically never delivers (the window ends on
150
- // the user's turn, or on tool results, which count as one) — use 'assistant', or
151
- // return the words from the tool itself.
152
-
153
- // LLM-activated body + tools (auto-attaches `read_skill` activation tool)
154
- const billing = defineSkill({
155
- id: 'billing',
156
- description: 'Use for refunds, subscriptions, invoices.',
157
- body: 'Confirm identity before processing refunds.',
158
- tools: [refundTool],
159
- });
160
-
161
- // Developer-supplied data (not behavior)
162
- const userProfile = defineFact({
163
- id: 'user',
164
- data: 'User: Alice (alice@example.com), Plan: Pro.',
165
- });
91
+ const graph = skillGraph()
92
+ .entry(triage, { when: (c) => /order/.test(c.userMessage) }) // where the turn STARTS
93
+ .route(triage, refunds, { onToolReturn: 'lookup_order' }) // a declared handoff
94
+ .build();
166
95
 
167
- agent
168
- .steering(tone)
169
- .instruction(urgent)
170
- .instruction(afterRedact)
171
- .skill(billing)
172
- .fact(userProfile);
96
+ Agent.create({ provider, model }).skillGraph(graph).build();
97
+ graph.toMermaid(); // declared === drawn
173
98
  ```
174
99
 
175
- Every flavor emits the same `agentfootprint.context.injected` event with `source` discriminating which factory produced it.
100
+ `.entry()` and `.route()` take the skill OBJECTS, not their ids. The object form is
101
+ the other door — `skillGraph({ skills, start, steps })` returns a finished graph with
102
+ nothing to chain.
103
+
104
+ A skill is active exactly while the cursor is on it — one skill's turn at a time. An `.entry(x)` with **no** `when` is the persistent base (`always`), on beside whatever the cursor is on.
105
+
106
+ **The cursor is a program counter, not a per-turn classifier.** Nine causes move it (or
107
+ decline to), reported as `cursorMove.by` on `agentfootprint.context.evaluated` and as
108
+ `outcome` on `routeRecorder().getHops()`:
109
+
110
+ `'entry'` (cold start) · `'route'` (a declared `from`-gated edge fired) · `'tool-proposal'`
111
+ (a TOOL RESULT proposed a transition and the graph accepted it) · `'model-pick'` (a
112
+ gate-accepted `read_skill`) · `'intent'` (the tier-2 scorer was decisive) · `'continuity'`
113
+ (the cursor inherited from the previous turn held) · `'decider'` (an out-of-band menu
114
+ resolver) · `'stay'` (nothing fired — sticky, and a recorded decision, not an absence) ·
115
+ `'none'` (no cursor at all: nothing to enter, or a `tree()`, which has no cursor).
116
+ `routeRecorder`'s `RouteOutcome` is those eight minus `'none'` (no cursor, no hop) plus
117
+ `'rejected'` — nine values. Precedence when several want it at once:
118
+ **declared edge > accepted tool proposal > model pick > stay.** A suppressed pick emits
119
+ `agentfootprint.skill.reroute_superseded`; a parallel batch matching different targets
120
+ emits `agentfootprint.skill.route_conflict`.
121
+
122
+ **The cursor is per RUN by default.** A second `run()` starts cold at the entry.
123
+ `.skillGraph(graph, { continuity: 'conversation' })` makes it span the conversation.
124
+
125
+ **`read_skill` has a three-way design, not one list.** Per iteration a skill is
126
+ *reachable* (named under "Reachable from here"), *refusable* (named under "Not reachable
127
+ from here" — a graph refusal is about WHERE THE CURSOR IS, so naming it lets the model
128
+ route in one step), or *hidden* (absent entirely — a hidden skill is about WHO IS ASKING,
129
+ and naming it would leak the shape of somebody else's permissions; needs a
130
+ `PermissionChecker` governing `skill_read`). **The enum stays the full catalog in every
131
+ case** — narrowing it would turn a policy refusal into a generic schema error the model
132
+ never reads.
133
+
134
+ A refused pick gets one teaching sentence back and moves nothing:
135
+
136
+ ```text
137
+ read_skill("audit-log") is not reachable from here. Reachable skills: billing. Pick one of these, or finish.
138
+ ```
176
139
 
177
- ## Memory
140
+ **The authority rule.** A tool result is written into the conversation once and then only
141
+ ages; the system prompt is rebuilt from nothing every iteration (`reactMode: 'dynamic'`,
142
+ the default, re-runs the InjectionEngine and all three slots). So standing instructions
143
+ belong in the recomposed surface. `reactMode: 'classic'` caches system-prompt and tools
144
+ after turn 1 — do **not** use it with skills.
178
145
 
179
- `defineMemory({ type, strategy, store })` ONE factory, dispatches `type × strategy.kind` onto the right pipeline. Multiple memories layer cleanly via per-id scope keys.
146
+ ### RAGretrieve, augment, generate
180
147
 
181
148
  ```typescript
182
- // Short-term sliding window the 90% case
183
- const shortTerm = defineMemory({
184
- id: 'short-term',
185
- type: MEMORY_TYPES.EPISODIC,
186
- strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 },
187
- store: new InMemoryStore(),
188
- });
189
-
190
- // Semantic recall — vector retrieval with strict threshold
191
- const facts = defineMemory({
192
- id: 'facts',
193
- type: MEMORY_TYPES.SEMANTIC,
194
- strategy: {
195
- kind: MEMORY_STRATEGIES.TOP_K,
196
- topK: 3,
197
- threshold: 0.7, // STRICT — empty when no match
198
- embedder: mockEmbedder(), // swap for openaiEmbedder() in prod
199
- },
200
- store: new InMemoryStore(),
201
- });
149
+ import { defineRAG } from 'agentfootprint'; // wiring lives on the main barrel
150
+ import { InMemoryStore, mockEmbedder } from 'agentfootprint/memory';
202
151
 
203
- // Causal UNIQUE TO AGENTFOOTPRINT.
204
- // Persists run snapshots so cross-run "why was X rejected?" follow-ups
205
- // answer from the STORED run: decisions (decide()/select() evidence +
206
- // route/skill provenance), tool calls, iterations, duration, tokens —
207
- // harvested automatically when a CAUSAL memory is mounted.
208
- const causal = defineMemory({
209
- id: 'causal',
210
- type: MEMORY_TYPES.CAUSAL,
211
- strategy: {
212
- kind: MEMORY_STRATEGIES.TOP_K,
213
- topK: 1,
214
- threshold: 0.7,
215
- embedder: mockEmbedder(),
216
- },
217
- store: new InMemoryStore(),
218
- projection: SNAPSHOT_PROJECTIONS.DECISIONS,
219
- });
220
-
221
- agent.memory(shortTerm).memory(facts).memory(causal);
222
-
223
- // `.selfExplain()` — the IN-CONVERSATION cousin of Causal: the agent answers a
224
- // follow-up "why did you do that?" about its OWN previous completed run, no store
225
- // needed. One builder call mounts a skill; the 5 trace tools (run_overview/
226
- // trace_node/who_wrote/get_value/trace_slice) appear ONLY on the iteration the user
227
- // asks "why", bound to the last completed run (never in-flight; a failed run still
228
- // explains). delegate:{provider,model} runs the trace-walk on a cheaper model.
229
- // Needs reactMode 'dynamic' (default). Causal = ANY past run, persisted, by similarity;
230
- // selfExplain = THIS conversation's last run, in-memory. (Causal similarity recall
231
- // needs a search()-capable store — only InMemoryStore today.) Docs: debug/self-explain.
232
- Agent.create({ provider, model }).system('...').tool(lookupOrder).selfExplain().build();
233
-
234
- // Multi-tenant identity — plumbs through agent.run:
235
- await agent.run({
236
- message: '...',
237
- identity: { tenant: 'acme', principal: 'alice', conversationId: 'thread-42' },
238
- });
152
+ Agent.create({ provider, model })
153
+ .rag(defineRAG({ id: 'docs', store: new InMemoryStore(), embedder: mockEmbedder(), topK: 5 }))
154
+ .build();
239
155
  ```
240
156
 
241
- The 4 **types**:
242
- - `EPISODIC` — raw conversation messages
243
- - `SEMANTIC` — extracted structured facts
244
- - `NARRATIVE` — beats / summaries of prior runs
245
- - `CAUSAL` — footprintjs decision-evidence snapshots ⭐
246
-
247
- The 7 **strategies**:
248
- - `WINDOW` (rule, last N) · `BUDGET` (decider, fit-to-tokens) · `SUMMARIZE` (LLM compresses older)
249
- - `TOP_K` (score-threshold) · `EXTRACT` (LLM distills on write)
250
- - `DECAY` (recency-weighted, planned) · `HYBRID` (compose multiple)
251
-
252
- ## MCP — `mcpClient` (connect to external MCP servers)
157
+ ### Control flow + patterns — compose runners
253
158
 
254
159
  ```typescript
255
- import { Agent } from 'agentfootprint';
256
- import { mcpClient } from 'agentfootprint/providers';
257
-
258
- const slack = await mcpClient({
259
- name: 'slack',
260
- transport: { transport: 'stdio', command: 'npx', args: ['@example/slack-mcp'] },
261
- });
160
+ import { Sequence, Parallel, Loop, Conditional, workflow, graph } from 'agentfootprint';
161
+ import { swarm, debate, reflection, selfConsistency, mapReduce, tot } from 'agentfootprint'; // patterns
262
162
 
263
- const agent = Agent.create({ provider })
264
- .tools(await slack.tools()) // pull ALL tools from server in one call
265
- .build();
163
+ const pipeline = Sequence.create().step('research', researchAgent).step('write', writerAgent).build();
266
164
 
267
- await agent.run({ message: '...' });
268
- await slack.close();
165
+ const desk = swarm({
166
+ agents: [{ id: 'research', runner: researchAgent }, { id: 'write', runner: writerAgent }],
167
+ route: ({ message }) => (/write/.test(message) ? 'write' : 'research'),
168
+ });
269
169
  ```
270
170
 
271
- Transports:
272
- - `{ transport: 'stdio', command, args, env?, cwd? }` — local subprocess
273
- - `{ transport: 'http', url, headers? }` — remote Streamable HTTP
171
+ ## Providers
274
172
 
275
- The `@modelcontextprotocol/sdk` peer-dep is **lazy-required** — zero
276
- runtime cost when MCP isn't used. Friendly install hint if missing.
173
+ ```typescript
174
+ import { mock, anthropic, openai, bedrock, ollama } from 'agentfootprint/providers';
277
175
 
278
- `agent.tools(arr)` is the bulk-register companion to `agent.tool(t)`.
279
- Tool-name uniqueness is validated at `.build()` across MCP servers +
280
- manual `.tool()` calls duplicates throw early.
176
+ const provider = process.env.NODE_ENV === 'production'
177
+ ? anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! })
178
+ : ollama('llama3.2'); // free local model; or mock({...}) for determinism
179
+ ```
281
180
 
282
- Server-side support (exposing your agent as an MCP tool to other LLMs)
283
- is a separate concern, not yet shipped.
181
+ `mock` takes `{ reply }` (one fixed answer), `{ replies: [...] }` (consumed in order — exhaustion throws loud), or `{ respond: (req) => … }` (build the answer from the request, including `toolCalls`).
284
182
 
285
- ## RAG — `defineRAG` + `indexDocuments`
183
+ ## Tools
286
184
 
287
185
  ```typescript
288
- import { defineRAG, indexDocuments, InMemoryStore, mockEmbedder } from 'agentfootprint';
289
-
290
- const store = new InMemoryStore();
291
- const embedder = mockEmbedder();
292
-
293
- // Seed corpus once at startup
294
- await indexDocuments(store, embedder, [
295
- { id: 'doc1', content: 'Refunds processed in 3 business days.' },
296
- { id: 'doc2', content: 'Pro plan: $20/month.' },
297
- ]);
298
-
299
- // Define retriever
300
- const docs = defineRAG({
301
- id: 'product-docs',
302
- store, embedder,
303
- topK: 3,
304
- threshold: 0.7, // STRICT — no fallback when nothing matches
305
- });
306
- // Retrieved chunks land in the SYSTEM-PROMPT slot, as one system message.
307
- // `asRole` was removed in 7.20.0 — it was never read, and passing it throws.
186
+ import { defineTool } from 'agentfootprint';
308
187
 
309
- // Wire `.rag()` is alias for `.memory()`, same plumbing
310
- agent.rag(docs);
188
+ const calculator = defineTool({
189
+ name: 'calculator', // `name`, not `id`
190
+ description: 'Perform arithmetic',
191
+ inputSchema: { type: 'object', properties: { expression: { type: 'string' } } },
192
+ execute: async ({ expression }: { expression: string }) => String(evaluate(expression)),
193
+ });
311
194
  ```
312
195
 
313
- `defineRAG` is sugar over `defineMemory({ type: SEMANTIC, strategy: TOP_K })` with RAG-friendly defaults. Distinction is intent: RAG = document corpus retrieval; `defineMemory` = conversation/run-state memory.
314
-
315
- ## Multi-agent via control flow
316
-
317
- There is **no** `MultiAgentSystem` class. Multi-agent = compositions of single Agents through the same control flow that connects any flowchart stages:
196
+ ## Observing a run
318
197
 
319
198
  ```typescript
320
- // Output flows downstream
321
- const pipeline = Sequence.create()
322
- .step(researcher)
323
- .step(writer)
324
- .step(editor)
325
- .build();
326
-
327
- // Multi-perspective with merge
328
- const tot = Parallel.create()
329
- .branch(thoughtAgent)
330
- .branch(thoughtAgent)
331
- .branch(thoughtAgent)
332
- .merge(rankerLLM)
333
- .build();
199
+ import { costRecorder, routeRecorder } from 'agentfootprint/observe';
334
200
 
335
- // Predicate-based routing
336
- const triage = Conditional.create()
337
- .when((ctx) => ctx.intent === 'billing', billingAgent)
338
- .when((ctx) => ctx.intent === 'tech', techAgent)
339
- .otherwise(generalAgent)
201
+ const agent = Agent.create({ provider, model })
202
+ .watch(routeRecorder({ id: 'routes' })) // recorders are factories, not classes
340
203
  .build();
341
204
 
342
- // Iterate with budget
343
- const refine = Loop.create()
344
- .body(critiqueAgent)
345
- .untilGuard((ctx) => ctx.qualityScore > 0.9)
346
- .maxIterations(5)
347
- .build();
205
+ agent.on('agentfootprint.context.evaluated', (e) => console.log(e.payload.activeIds));
348
206
  ```
349
207
 
350
- ## Named patterns recipes ship as runnable examples
208
+ **93 typed events across 21 domains.** Two subscription shapes and no third:
209
+ `'*'` (every event) and `'agentfootprint.<domain>.*'` (one domain). **`'agentfootprint.*'`
210
+ is not a pattern** — TypeScript rejects it, and at runtime it would match nothing.
351
211
 
212
+ ```typescript
213
+ agent.on('*', (e) => log(e));
214
+ agent.on('agentfootprint.stream.*', (e) => log(e)); // tool_start, tool_end, deltas
215
+ agent.on('agentfootprint.agent.turn_end', (e) =>
216
+ console.log(`${e.payload.iterationCount} iterations`));
352
217
  ```
353
- ReAct = Agent (default loop)
354
- Reflexion = Sequence(Agent, critique-LLM, Agent)
355
- Tree-of-Thoughts = Parallel(Agent × N) + rank
356
- Self-Consistency = Parallel(Agent × N) + majority-vote
357
- Debate = Loop(Agent × 2 + judge)
358
- Map-Reduce = Parallel(Agent × N) + merge
359
- Swarm = Agent whose tools are other Agents
360
- ```
361
-
362
- Browse [`examples/patterns/`](examples/patterns/) — every pattern is a runnable end-to-end test.
363
218
 
364
- ## Pause / resume (HITL)
219
+ ## Human in the loop
365
220
 
366
221
  ```typescript
367
- import { askHuman, pauseHere, isPaused } from 'agentfootprint';
368
-
369
- const approveTool = defineTool({
370
- schema: { name: 'approve', description: 'Ask a human.', inputSchema: { ... } },
371
- execute: askHuman({ severity: 'high' }),
372
- });
222
+ import { askHuman, isPaused, checkInApproved } from 'agentfootprint';
373
223
 
374
224
  const result = await agent.run({ message: 'Refund $500?' });
375
225
  if (isPaused(result)) {
376
- const checkpoint = result.checkpoint; // JSON-serializable
377
- // Persist to Redis/DB; later, on possibly different server:
378
- const final = await agent.resume(checkpoint, { approved: true });
226
+ const final = await agent.resume(result.checkpoint, checkInApproved({ by: 'maya' }));
379
227
  }
380
228
  ```
381
229
 
382
- ## Observability — 93 typed events × 21 domains
230
+ ## Resilience
383
231
 
384
232
  ```typescript
385
- agent.on('agentfootprint.context.injected', (e) =>
386
- console.log(`[${e.payload.source}] landed in ${e.payload.slot}`));
387
- agent.on('agentfootprint.stream.tool_start', (e) =>
388
- console.log(`→ ${e.payload.toolName}(${JSON.stringify(e.payload.args)})`));
389
- agent.on('agentfootprint.agent.turn_end', (e) =>
390
- console.log(`[${e.payload.iterationCount} iter, tokens=${e.payload.totalInputTokens}+${e.payload.totalOutputTokens}]`));
233
+ import { withRetry, withFallback, withCircuitBreaker } from 'agentfootprint/resilience';
391
234
 
392
- // Wildcards: '*' for every event, or 'agentfootprint.<domain>.*' per-domain.
393
- // 'agentfootprint.*' is NOT a valid pattern — silently matches nothing.
394
- agent.on('*', (e) => log(e));
395
- agent.on('agentfootprint.stream.*', (e) => log(e));
235
+ const reliable = withRetry(provider, { maxAttempts: 3 });
236
+ const resilient = withFallback(primary, backup);
396
237
  ```
397
238
 
398
- ## Anti-patterns — Don't
239
+ ## Anti-Patterns
399
240
 
400
- - ❌ **Don't ship a `ReflexionAgent` class.** Compose `Sequence(Agent, critique-LLM, Agent)`.
401
- - ❌ **Don't use `agent.run('string')`**use `agent.run({ message: '...', identity? })`.
402
- - ❌ **Don't import from stale subpaths** (`agentfootprint/instructions`, `agentfootprint/observe`, `agentfootprint/security`). Top-level barrel covers everything: `from 'agentfootprint'`.
403
- - ❌ **Don't use `.memoryPipeline(pipeline)`**that's the v1 API. Use `.memory(defineMemory({...}))`.
404
- - ❌ **Don't fall back when TopK threshold returns nothing.** Strict semantics: garbage past context > none is wrong.
405
- - ❌ **Don't store closures or class instances in scope** TransactionBuffer can't clone functions. Memory-store entries serialize to JSON.
406
- - ❌ **Don't add new event types per feature.** Route through `agentfootprint.context.injected` with a new `source` value.
407
- - ❌ **Don't reach for `agentObservability()`** — it's a v1 name. Use the recorder factories: `agentRecorder({...})`, `costRecorder({...})`, etc.
241
+ - Don't use `id`/`handler` on `defineTool` it's `name`/`execute`
242
+ - Don't call `mock([...])` with an array it's `mock({ replies: [...] })`
243
+ - Don't import any provider (`mock` included) from the main barrel they live on `agentfootprint/providers`
244
+ - Don't `new` a recorder they're lowercase factories attached via `.watch()`
245
+ - Don't write `.entry(x, { when: () => true })` for an always-on skill omit `when`, or use `.steering()`
246
+ - Don't post-process executionuse recorders
408
247
 
409
- ## Decision tree pick the right tool
410
-
411
- | Goal | Use |
412
- |---|---|
413
- | One-shot LLM call (summarization, classification) | `LLMCall` |
414
- | Loop with tools (research, code, anything iterative) | `Agent` |
415
- | Two LLM calls in series with output flowing | `Sequence` |
416
- | Multiple critics, merge with LLM | `Parallel` |
417
- | Route to specialist by intent | `Conditional` |
418
- | Iterate until quality bar | `Loop` |
419
- | Output format / persona / safety policy | `defineSteering` |
420
- | Rule that fires when predicate matches | `defineInstruction` |
421
- | LLM activates a body of expertise + its tools | `defineSkill` |
422
- | Inject user profile / current time / env data | `defineFact` |
423
- | Remember last N turns of conversation | `defineMemory({ type: EPISODIC, strategy: WINDOW })` |
424
- | Semantic recall via embeddings | `defineMemory({ type: SEMANTIC, strategy: TOP_K })` |
425
- | Cross-run "why?" replay | `defineMemory({ type: CAUSAL, strategy: TOP_K })` ⭐ |
426
- | Long conversation overflows context | `defineMemory({ type: EPISODIC, strategy: SUMMARIZE })` |
427
-
428
- ## Build & test
248
+ ## Checking your own wiring
429
249
 
430
250
  ```bash
431
- npm install agentfootprint footprintjs
432
- npm test # vitest run 1100+ tests
433
- npm run example examples/... # run a single example end-to-end
434
- npm run examples:run-all # run every example (33 of them)
251
+ npx agentfootprint-lint-tools tools.json # confusable tool catalog — the CI gate
252
+ npx agentfootprint-index ./docs --to ./corpus.db # build a RAG corpus at boot time
435
253
  ```
436
254
 
437
- ## Roadmap (informs what to defer)
255
+ ```typescript
256
+ const report = graph.checkup({ knownTools: ['lookup_order'] }); // unreachable skills, unknown edges, dead entries
257
+ if (!report.ok) throw new Error(formatCheckup(report));
258
+ ```
438
259
 
439
- - **v2.0 (current)** — primitives + compositions + InjectionEngine + Memory (incl. Causal) + 6 providers + 33 examples
440
- - **v2.1** — RAG flavor (`defineRAG`) · Redis memory adapter · MCP integration · CircuitBreaker · 3-tier output fallback
441
- - **v2.2** — Governance (Policy + BudgetTracker) · DynamoDB / Postgres / Pinecone adapters
442
- - **v2.3** — Causal training-data exports (`exportForTraining({ format: 'sft' | 'dpo' | 'process' })`)
443
- - **v2.4+** — Deep Agents · A2A protocol · Lens UI integration
260
+ ## Going deeper
444
261
 
445
- When in doubtread [`examples/`](examples/), every file is a runnable spec.
262
+ The full architecture of the skill graph the three surfaces, the authority rule, the
263
+ nine cursor causes, the three-way `read_skill`, and a worked refusal taken from a real
264
+ run — is published as **Skill graph architecture**:
265
+ <https://footprintjs.github.io/agentfootprint/docs/build/skill-graph-architecture/>.
266
+ Every capability claim there carries a status — `shipped` / `opt-in` /
267
+ `application-provided` / `planned` — and every code block is type-checked against the
268
+ shipped types at build. Read it rather than this file when the question is "how does
269
+ routing actually work"; read this file for what to reach for and what does not exist.