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.
- package/ai-instructions/claude-code/SKILL.md +177 -353
- package/dist/adapters/code/local.js +15 -0
- package/dist/adapters/code/local.js.map +1 -1
- package/dist/artifacts/placement.js.map +1 -1
- package/dist/artifacts/streaming.js +30 -1
- package/dist/artifacts/streaming.js.map +1 -1
- package/dist/artifacts/types.js.map +1 -1
- package/dist/artifacts/wants.js +62 -5
- package/dist/artifacts/wants.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +30 -4
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/codeRunnerTool.js +48 -2
- package/dist/core/codeRunnerTool.js.map +1 -1
- package/dist/core/tools.js.map +1 -1
- package/dist/doors/skill-graph.js +55 -6
- package/dist/doors/skill-graph.js.map +1 -1
- package/dist/esm/adapters/code/local.d.ts +15 -0
- package/dist/esm/adapters/code/local.js +15 -0
- package/dist/esm/adapters/code/local.js.map +1 -1
- package/dist/esm/adapters/types.d.ts +14 -0
- package/dist/esm/artifacts/placement.d.ts +10 -0
- package/dist/esm/artifacts/placement.js.map +1 -1
- package/dist/esm/artifacts/streaming.d.ts +30 -1
- package/dist/esm/artifacts/streaming.js +30 -1
- package/dist/esm/artifacts/streaming.js.map +1 -1
- package/dist/esm/artifacts/types.d.ts +7 -1
- package/dist/esm/artifacts/types.js.map +1 -1
- package/dist/esm/artifacts/wants.d.ts +11 -3
- package/dist/esm/artifacts/wants.js +62 -5
- package/dist/esm/artifacts/wants.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.js +30 -4
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/codeRunnerTool.js +48 -2
- package/dist/esm/core/codeRunnerTool.js.map +1 -1
- package/dist/esm/core/tools.d.ts +7 -0
- package/dist/esm/core/tools.js.map +1 -1
- package/dist/esm/doors/skill-graph.d.ts +55 -6
- package/dist/esm/doors/skill-graph.js +55 -6
- package/dist/esm/doors/skill-graph.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillGraph.d.ts +7 -1
- package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/esm/lib/injection-engine/types.d.ts +17 -0
- package/dist/esm/lib/injection-engine/types.js.map +1 -1
- package/dist/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/lib/injection-engine/types.js.map +1 -1
- package/dist/types/adapters/code/local.d.ts +15 -0
- package/dist/types/adapters/code/local.d.ts.map +1 -1
- package/dist/types/adapters/types.d.ts +14 -0
- package/dist/types/adapters/types.d.ts.map +1 -1
- package/dist/types/artifacts/placement.d.ts +10 -0
- package/dist/types/artifacts/placement.d.ts.map +1 -1
- package/dist/types/artifacts/streaming.d.ts +30 -1
- package/dist/types/artifacts/streaming.d.ts.map +1 -1
- package/dist/types/artifacts/types.d.ts +7 -1
- package/dist/types/artifacts/types.d.ts.map +1 -1
- package/dist/types/artifacts/wants.d.ts +11 -3
- package/dist/types/artifacts/wants.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/tools.d.ts +7 -0
- package/dist/types/core/tools.d.ts.map +1 -1
- package/dist/types/doors/skill-graph.d.ts +55 -6
- package/dist/types/doors/skill-graph.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillGraph.d.ts +7 -1
- package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/types.d.ts +17 -0
- package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,445 +1,269 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agentfootprint
|
|
3
|
-
description: Use when building
|
|
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 —
|
|
6
|
+
# agentfootprint — The Explainable Agent Framework
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
20
|
+
## Read this first — what does NOT exist
|
|
24
21
|
|
|
25
|
-
|
|
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
|
-
|
|
|
26
|
+
| You will reach for | The reality |
|
|
28
27
|
|---|---|
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
42
|
+
## Core Concepts
|
|
72
43
|
|
|
73
|
-
|
|
44
|
+
### LLMCall — a single LLM call, no tools
|
|
74
45
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
|
73
|
+
const result = await agent.run({ message: 'Weather in Paris?' });
|
|
111
74
|
```
|
|
112
75
|
|
|
113
|
-
|
|
76
|
+
### Context — facts, steering, skills, and declared routing
|
|
114
77
|
|
|
115
78
|
```typescript
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
+
### RAG — retrieve, augment, generate
|
|
180
147
|
|
|
181
148
|
```typescript
|
|
182
|
-
//
|
|
183
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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 {
|
|
256
|
-
import {
|
|
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
|
|
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
|
-
|
|
268
|
-
|
|
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
|
-
|
|
272
|
-
- `{ transport: 'stdio', command, args, env?, cwd? }` — local subprocess
|
|
273
|
-
- `{ transport: 'http', url, headers? }` — remote Streamable HTTP
|
|
171
|
+
## Providers
|
|
274
172
|
|
|
275
|
-
|
|
276
|
-
|
|
173
|
+
```typescript
|
|
174
|
+
import { mock, anthropic, openai, bedrock, ollama } from 'agentfootprint/providers';
|
|
277
175
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
183
|
+
## Tools
|
|
286
184
|
|
|
287
185
|
```typescript
|
|
288
|
-
import {
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
219
|
+
## Human in the loop
|
|
365
220
|
|
|
366
221
|
```typescript
|
|
367
|
-
import { askHuman,
|
|
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
|
|
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
|
-
##
|
|
230
|
+
## Resilience
|
|
383
231
|
|
|
384
232
|
```typescript
|
|
385
|
-
|
|
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
|
-
|
|
393
|
-
|
|
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-
|
|
239
|
+
## Anti-Patterns
|
|
399
240
|
|
|
400
|
-
-
|
|
401
|
-
-
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
-
|
|
405
|
-
-
|
|
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 execution — use recorders
|
|
408
247
|
|
|
409
|
-
##
|
|
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
|
-
|
|
432
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|