agentfootprint 2.11.4 → 2.11.5
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/README.md +322 -144
- package/dist/core/Agent.js +44 -1
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +67 -1
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/agent/stages/callLLM.js +45 -17
- package/dist/core/agent/stages/callLLM.js.map +1 -1
- package/dist/core/agent/stages/reliabilityExecution.js +291 -0
- package/dist/core/agent/stages/reliabilityExecution.js.map +1 -0
- package/dist/esm/core/Agent.js +44 -1
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.js +67 -1
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/stages/callLLM.js +45 -17
- package/dist/esm/core/agent/stages/callLLM.js.map +1 -1
- package/dist/esm/core/agent/stages/reliabilityExecution.js +287 -0
- package/dist/esm/core/agent/stages/reliabilityExecution.js.map +1 -0
- package/dist/types/core/Agent.d.ts +9 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +61 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/stages/callLLM.d.ts +8 -0
- package/dist/types/core/agent/stages/callLLM.d.ts.map +1 -1
- package/dist/types/core/agent/stages/reliabilityExecution.d.ts +66 -0
- package/dist/types/core/agent/stages/reliabilityExecution.d.ts.map +1 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
<p align="center">
|
|
3
|
-
<
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/hero-dark.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/hero-light.svg">
|
|
6
|
+
<img alt="agentfootprint mascot composing context flavors (Skills, Steering, Guardrails, RAG, Tool APIs, Memory) into three structured LLM slots (system, messages, tools) — the central abstraction, visualized." src="docs/assets/hero-light.svg" width="100%"/>
|
|
7
|
+
</picture>
|
|
4
8
|
</p>
|
|
5
9
|
|
|
6
|
-
<h1 align="center">
|
|
10
|
+
<h1 align="center">Agentfootprint</h1>
|
|
7
11
|
|
|
8
12
|
<p align="center">
|
|
9
|
-
<strong>
|
|
13
|
+
<strong>We abstract context engineering — and hand back the trace.</strong><br/>
|
|
14
|
+
<strong>Live</strong> to develop · <strong>offline</strong> to monitor · <strong>detailed</strong> to improve.
|
|
10
15
|
</p>
|
|
11
16
|
|
|
12
17
|
<p align="center">
|
|
@@ -19,82 +24,248 @@
|
|
|
19
24
|
|
|
20
25
|
---
|
|
21
26
|
|
|
22
|
-
## What
|
|
27
|
+
## 1. What we abstract
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
When you build an Agentic Application, you collect domain-specific data and instructions, then wire them up based on what your system receives.
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
That data and those instructions wear many names — **Skills · Steering · Guardrails · RAG · Tool APIs · Memory** — with more on the way. But they all do the same thing: they **inject into one of three slots** in the LLM call (`system`, `messages`, `tools`).
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
So we abstracted the injection itself.
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
<p align="center">
|
|
36
|
+
<picture>
|
|
37
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/triggers-dark.svg">
|
|
38
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/triggers-light.svg">
|
|
39
|
+
<img alt="agentfootprint — Every LLM call has 3 fixed slots (system, messages, tools). Every flavor lands in one slot under one of 4 fixed triggers (always · rule · on-tool-return · llm-activated). Sparkle streams flow from each trigger lane down to a specific pill inside its destination slot — same slot can hold pills from different triggers (RAG via rule, Instruction via on-tool-return), and the same flavor (Skill) can land in different slots." src="docs/assets/triggers-light.svg" width="100%"/>
|
|
40
|
+
</picture>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
The abstraction is three rules:
|
|
44
|
+
|
|
45
|
+
1. **Three slots are fixed.** `system`, `messages`, `tools` — the LLM API surface.
|
|
46
|
+
2. **N flavors are open.** You declare what you have. Tomorrow's flavor (few-shot, reflection, persona, A2A handoff…) plugs in the same way.
|
|
47
|
+
3. **Rules decide *where* and *when*.** You provide the rules. We collect your data, fire the right one, land it in the right slot at the right iteration.
|
|
48
|
+
|
|
49
|
+
That's the whole model: `Injection = slot × trigger × cache`.
|
|
50
|
+
|
|
51
|
+
- **Slot** — which of the 3 LLM API regions the content lands in (`system` / `messages` / `tools`).
|
|
52
|
+
- **Trigger** — when the content fires (see below).
|
|
53
|
+
- **Cache** — how stable the content is across iterations. The framework places provider cache markers for you — stable content gets 80–90% cheaper prefixes.
|
|
54
|
+
|
|
55
|
+
### Triggers — static or runtime
|
|
56
|
+
|
|
57
|
+
Every rule fires from one of two places:
|
|
58
|
+
|
|
59
|
+
- **Static** — set at build time, fires every iteration *(always-on)*
|
|
60
|
+
- **Runtime** — fires from something that happens during the run:
|
|
61
|
+
- a tool response *(after_tool)*
|
|
62
|
+
- an LLM activation *(read_skill)*
|
|
63
|
+
- a predicate over scope *(rule)*
|
|
31
64
|
|
|
32
|
-
|
|
65
|
+
Four triggers, two flavors:
|
|
66
|
+
|
|
67
|
+
| # | Trigger | Fires when | One-line example | Default slot |
|
|
68
|
+
|---|---|---|---|---|
|
|
69
|
+
| 1 | `always` *(static)* | Every iteration | `.steering('You are a triage agent…')` | `system` |
|
|
70
|
+
| 2 | `rule` *(runtime — predicate)* | Your rule returns true | `.rag({ when: s => /price\|refund/.test(s.userQuery), source: docs })` | `messages` |
|
|
71
|
+
| 3 | `on-tool-return` *(runtime — lifecycle)* | After a specific tool returns | `.instruction({ after: 'search_db', text: 'Cite source IDs.' })` | `messages` |
|
|
72
|
+
| 4 | `llm-activated` *(runtime — agent-driven)* | LLM calls `read_skill('id')` | `.skill({ id: 'refund-policy', activatedBy: 'read_skill' })` | `messages` (body) |
|
|
73
|
+
|
|
74
|
+
> **Slot is a default, not a coupling — same flavor lives in any slot, strategy is config.**
|
|
75
|
+
> A `Skill` can live in:
|
|
76
|
+
> - `tools` slot → schema only, LLM discovers it via `read_skill` — trigger `always`
|
|
77
|
+
> - `messages` slot → body injected on activation — trigger `llm-activated`
|
|
78
|
+
> - `system` slot → body baked into the system prompt as permanent steering — trigger `always`
|
|
79
|
+
|
|
80
|
+
**3 slots × 4 triggers × N flavors = the entire context-engineering surface.** Locate any agent feature on this grid; that's enough to model it.
|
|
33
81
|
|
|
34
82
|
---
|
|
35
83
|
|
|
36
|
-
##
|
|
84
|
+
## 2. Why we chose this abstraction
|
|
37
85
|
|
|
38
|
-
|
|
86
|
+
The agent space has many credible primary abstractions:
|
|
39
87
|
|
|
40
|
-
| Framework |
|
|
41
|
-
|
|
42
|
-
| **
|
|
43
|
-
| **
|
|
44
|
-
| **
|
|
45
|
-
| **
|
|
46
|
-
| **
|
|
88
|
+
| Framework | What it abstracts |
|
|
89
|
+
|---|---|
|
|
90
|
+
| **LangChain** | Pipelines of composable components |
|
|
91
|
+
| **LangGraph** | State machines of nodes and edges |
|
|
92
|
+
| **CrewAI · AutoGen** | Crews of role-playing agents |
|
|
93
|
+
| **Mastra · Genkit · Pydantic AI** | Typed full-stack bundles |
|
|
94
|
+
| **DSPy** | Compiled prompts |
|
|
95
|
+
| **Inngest AgentKit** | Durable workflows |
|
|
96
|
+
|
|
97
|
+
We didn't have to choose between them.
|
|
98
|
+
|
|
99
|
+
agentfootprint is built on **footprintjs** — the flowchart pattern for backend code. footprintjs gives us every one of those abstractions out of the box:
|
|
100
|
+
|
|
101
|
+
- **Composition** — `Sequence` · `Parallel` · `Conditional` · `Loop`
|
|
102
|
+
- **State machines** — the ReAct loop *is* a flowchart
|
|
103
|
+
- **Multi-agent crews** — compose Agents through control flow, no special class needed
|
|
104
|
+
- **Durable workflows** — `pauseHere()` plus JSON-portable `resume()`
|
|
105
|
+
- **Typed observation** — 47+ events for free, because the framework owns the loop
|
|
47
106
|
|
|
48
|
-
|
|
107
|
+
So we used the budget those abstractions would have cost us to invest deeply in something they all leave to the developer: **the injection loop.**
|
|
108
|
+
|
|
109
|
+
> **We abstract context engineering.**
|
|
110
|
+
> Live to develop · offline to monitor · detailed to improve — handed back as the trace.
|
|
111
|
+
|
|
112
|
+
### The reason — agents have a new class of bug
|
|
113
|
+
|
|
114
|
+
For fifty years, software bugs have been **logic errors**. A wrong condition, a missed edge case, an off-by-one. You step through the code until you find the bad branch.
|
|
115
|
+
|
|
116
|
+
LLM-powered apps add a second class of bug: **contextual errors.** The code is correct. The model is correct. The answer is wrong because **the LLM's decision rests on context that was ambiguous, confusing, or misleading at the moment of inference.**
|
|
117
|
+
|
|
118
|
+
Tracking *which content the model actually saw, and why,* is the entire debugging job. Without it, the failure mode is invisible:
|
|
119
|
+
|
|
120
|
+
- The wrong instruction landed in the `system` slot — the model followed the wrong rule.
|
|
121
|
+
- A predicate fired one iteration too early — context arrived with stale assumptions.
|
|
122
|
+
- A skill body was missing when the LLM called `read_skill` — the model invented its own.
|
|
123
|
+
- The cache prefix invalidated — a stable instruction got silently rewritten with a stale version.
|
|
124
|
+
- A tool returned — but the on-tool-return injection that explains how to interpret the result never fired.
|
|
125
|
+
|
|
126
|
+
**The model doesn't tell you which of these went wrong. It just gives you the wrong answer.**
|
|
127
|
+
|
|
128
|
+
You can't step through that with a debugger. By the time you read the response, the context that produced it is gone unless something recorded it.
|
|
129
|
+
|
|
130
|
+
That's the gap agentfootprint fills. A framework that owns the control flow can debug logic errors. A framework that owns the *injection* can debug contextual errors — because every injection is a typed event with a where, when, why, and how-it-cached.
|
|
131
|
+
|
|
132
|
+
### What that buys you
|
|
133
|
+
|
|
134
|
+
Because we own the injection, every LLM call backtracks to four typed answers:
|
|
135
|
+
|
|
136
|
+
- **What** was injected
|
|
137
|
+
- **Who** triggered it (which rule)
|
|
138
|
+
- **When** it fired
|
|
139
|
+
- **How** it landed — slot, position, cache
|
|
140
|
+
|
|
141
|
+
Same trace, three workflows:
|
|
142
|
+
|
|
143
|
+
- **Live — debug as you build.** See exactly which injection produced which token, which predicate fired this iteration, which prefix actually got cached.
|
|
144
|
+
- **Offline — monitor what shipped.** Replay any past run from its trace. Alert on drift. Attribute cost per injection.
|
|
145
|
+
- **Detailed — improve via export.** Every successful trajectory is labeled training data for SFT, DPO, or RL — no separate data-collection phase.
|
|
146
|
+
|
|
147
|
+
And a fourth, novel: **the agent can read its own trace.** Six months after the agent rejected loan #42, *"why did you reject it?"* answers from the recorded evidence (`creditScore=580`, `threshold=600`), not a rerun. Causal memory turns the trace into the agent's working memory.
|
|
49
148
|
|
|
50
149
|
---
|
|
51
150
|
|
|
52
|
-
##
|
|
151
|
+
## Where this sits
|
|
53
152
|
|
|
54
|
-
|
|
153
|
+
You'll find pieces of agentfootprint in two adjacent categories of framework.
|
|
55
154
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```
|
|
155
|
+
- **Model-driven agent runners** let the LLM drive the loop. We ship one — Dynamic ReAct.
|
|
156
|
+
- **Low-level orchestration frameworks** let you wire nodes and edges. We ship the same compositions one level up: `Sequence` · `Parallel` · `Conditional` · `Loop`.
|
|
59
157
|
|
|
60
|
-
|
|
158
|
+
What neither category ships: the **Injection primitive** (Beat 1) and the **causal trace** (Beat 4). Both are free side effects of owning the runtime loop.
|
|
61
159
|
|
|
62
|
-
|
|
63
|
-
|
|
160
|
+
> agentfootprint = a model-driven agent runner + compositional orchestration + context engineering as a first-class layer, trace baked in.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 3. How do I design my agent or system of agents?
|
|
165
|
+
|
|
166
|
+
Two scales — same alphabet. Four control flows are the entire vocabulary.
|
|
167
|
+
|
|
168
|
+
<table>
|
|
169
|
+
<tr>
|
|
170
|
+
<td width="50%" align="center">
|
|
171
|
+
<picture>
|
|
172
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/sequence-dark.svg">
|
|
173
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/sequence-light.svg">
|
|
174
|
+
<img alt="Sequence — linear chain A → B → C." src="docs/assets/sequence-light.svg" width="100%"/>
|
|
175
|
+
</picture>
|
|
176
|
+
</td>
|
|
177
|
+
<td width="50%">
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { Sequence } from 'agentfootprint';
|
|
181
|
+
|
|
182
|
+
const flow = Sequence.create()
|
|
183
|
+
.step('a', stageA)
|
|
184
|
+
.step('b', stageB)
|
|
185
|
+
.step('c', stageC)
|
|
186
|
+
.build();
|
|
64
187
|
```
|
|
65
188
|
|
|
66
|
-
|
|
189
|
+
</td>
|
|
190
|
+
</tr>
|
|
191
|
+
<tr>
|
|
192
|
+
<td width="50%" align="center">
|
|
193
|
+
<picture>
|
|
194
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/parallel-dark.svg">
|
|
195
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/parallel-light.svg">
|
|
196
|
+
<img alt="Parallel — fan-out then fan-in across N agents." src="docs/assets/parallel-light.svg" width="100%"/>
|
|
197
|
+
</picture>
|
|
198
|
+
</td>
|
|
199
|
+
<td width="50%">
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { Parallel } from 'agentfootprint';
|
|
203
|
+
|
|
204
|
+
const fan = Parallel.create()
|
|
205
|
+
.branch('web', searchWeb)
|
|
206
|
+
.branch('docs', searchDocs)
|
|
207
|
+
.mergeWithFn(synthesizer)
|
|
208
|
+
.build();
|
|
209
|
+
```
|
|
67
210
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
211
|
+
</td>
|
|
212
|
+
</tr>
|
|
213
|
+
<tr>
|
|
214
|
+
<td width="50%" align="center">
|
|
215
|
+
<picture>
|
|
216
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/conditional-dark.svg">
|
|
217
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/conditional-light.svg">
|
|
218
|
+
<img alt="Conditional — diamond gate routes to one of N branches based on a predicate." src="docs/assets/conditional-light.svg" width="100%"/>
|
|
219
|
+
</picture>
|
|
220
|
+
</td>
|
|
221
|
+
<td width="50%">
|
|
71
222
|
|
|
72
|
-
|
|
223
|
+
```typescript
|
|
224
|
+
import { Conditional } from 'agentfootprint';
|
|
73
225
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
└──────┼────────────┼────────────┼───┘
|
|
80
|
-
│ │ │
|
|
81
|
-
Injection Injection Injection
|
|
82
|
-
▲
|
|
83
|
-
│
|
|
84
|
-
always · rule · on-tool-return · llm-activated
|
|
226
|
+
const router = Conditional.create()
|
|
227
|
+
.when('billing', s => s.intent === 'billing', billingAgent)
|
|
228
|
+
.when('tech', s => s.intent === 'tech', techAgent)
|
|
229
|
+
.otherwise('default', defaultAgent)
|
|
230
|
+
.build();
|
|
85
231
|
```
|
|
86
232
|
|
|
87
|
-
|
|
233
|
+
</td>
|
|
234
|
+
</tr>
|
|
235
|
+
<tr>
|
|
236
|
+
<td width="50%" align="center">
|
|
237
|
+
<picture>
|
|
238
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/loop-dark.svg">
|
|
239
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/loop-light.svg">
|
|
240
|
+
<img alt="Loop — body cycles back from end to start until a condition is met." src="docs/assets/loop-light.svg" width="100%"/>
|
|
241
|
+
</picture>
|
|
242
|
+
</td>
|
|
243
|
+
<td width="50%">
|
|
88
244
|
|
|
89
|
-
|
|
245
|
+
```typescript
|
|
246
|
+
import { Loop } from 'agentfootprint';
|
|
90
247
|
|
|
91
|
-
|
|
248
|
+
const reflexion = Loop.create()
|
|
249
|
+
.repeat(thinkAgent)
|
|
250
|
+
.until(s => s.satisfied)
|
|
251
|
+
.build();
|
|
252
|
+
```
|
|
92
253
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
254
|
+
</td>
|
|
255
|
+
</tr>
|
|
256
|
+
</table>
|
|
257
|
+
|
|
258
|
+
### Inside one agent — Dynamic vs Classic ReAct
|
|
259
|
+
|
|
260
|
+
<p align="center">
|
|
261
|
+
<picture>
|
|
262
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/dynamic-vs-classic-dark.svg">
|
|
263
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/dynamic-vs-classic-light.svg">
|
|
264
|
+
<img alt="Classic ReAct vs Dynamic ReAct loop topology — same 5 stages (SystemPrompt, Messages, Tools, CallLLM, Route → ExecuteTools/Finalize), but the loop edge differs: Classic returns to CallLLM only (slots frozen at 12 tools every iteration), Dynamic returns to SystemPrompt (slots recompose, tools shrink from 1 to 5 as skills activate)." src="docs/assets/dynamic-vs-classic-light.svg" width="100%"/>
|
|
265
|
+
</picture>
|
|
266
|
+
</p>
|
|
96
267
|
|
|
97
|
-
|
|
268
|
+
**Same five stages on both sides. Only one thing differs — where the loop returns.** Classic ReAct loops back to `CallLLM` and slots stay frozen. Dynamic ReAct (agentfootprint) loops back to `SystemPrompt`, so injections that fired on the previous tool result recompose the next prompt. Per-iteration recomposition is also the structural prerequisite for the cache layer.
|
|
98
269
|
|
|
99
270
|
```text
|
|
100
271
|
Classic ReAct Dynamic ReAct
|
|
@@ -104,9 +275,85 @@ iter 2: 12 tools shown iter 2: 5 tools (skill activated)
|
|
|
104
275
|
iter 3: 12 tools shown iter 3: 5 tools
|
|
105
276
|
```
|
|
106
277
|
|
|
107
|
-
|
|
278
|
+
> 📖 [Dynamic ReAct guide](https://footprintjs.github.io/agentfootprint/guides/dynamic-react/) · [Key concepts](https://footprintjs.github.io/agentfootprint/getting-started/key-concepts/)
|
|
279
|
+
|
|
280
|
+
### Multi-agent — compose with the alphabet
|
|
281
|
+
|
|
282
|
+
<p align="center">
|
|
283
|
+
<picture>
|
|
284
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/compose-dark.svg">
|
|
285
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/compose-light.svg">
|
|
286
|
+
<img alt="A custom research agent built from the same 4 control flows: input flows into a Conditional gate (plan more research?), which fans out to a Parallel block (search_web, search_docs, search_kb), then chains into a Sequence (synthesize → critique), and a Loop arrow returns from the end back to the Conditional gate so the agent iterates until satisfied. Formula: Loop( Conditional(plan?) → Parallel(search_web, search_docs, search_kb) → Sequence(synth → critique) )." src="docs/assets/compose-light.svg" width="100%"/>
|
|
287
|
+
</picture>
|
|
288
|
+
</p>
|
|
289
|
+
|
|
290
|
+
Pick the flows that match your problem. Chain them. **That's your Agentic Application.**
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
const research = Loop.create()
|
|
294
|
+
.repeat(Sequence.create().step('plan', plan).step('search', searchAll).build())
|
|
295
|
+
.until(s => s.satisfied).build();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Same `.create().method().build()` shape as the four rows above — just composed.
|
|
299
|
+
|
|
300
|
+
### Named patterns — also compositions of the same 4
|
|
301
|
+
|
|
302
|
+
<p align="center">
|
|
303
|
+
<picture>
|
|
304
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/patterns-dark.svg">
|
|
305
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/patterns-light.svg">
|
|
306
|
+
<img alt="6 named multi-agent patterns reduce to compositions of the same 4 control flows: Swarm = Loop(Parallel(Agent×N) → merge); Tree-of-Thoughts = Loop(Parallel(Agent×N) → Conditional(score)); Reflexion = Loop(Agent → Conditional(critique) → Agent); Debate = Parallel(Agent_pro, Agent_con) → Agent_judge; Router = Conditional → Agent_A | Agent_B | Agent_C; Hierarchical = Agent_planner → Sequence(Agent_worker×N) → synth." src="docs/assets/patterns-light.svg" width="100%"/>
|
|
307
|
+
</picture>
|
|
308
|
+
</p>
|
|
309
|
+
|
|
310
|
+
The patterns the field knows reduce to the same alphabet:
|
|
311
|
+
|
|
312
|
+
| Pattern | Composition |
|
|
313
|
+
|---|---|
|
|
314
|
+
| **Swarm** | `Loop( Parallel( Agent×N ) → merge )` |
|
|
315
|
+
| **Tree-of-Thoughts** | `Loop( Parallel( Agent×N ) → Conditional(score) )` |
|
|
316
|
+
| **Reflexion** | `Loop( Agent → Conditional(critique) → Agent )` |
|
|
317
|
+
| **Debate** | `Parallel( Agent_pro, Agent_con ) → Agent_judge` |
|
|
318
|
+
| **Router** | `Conditional → Agent_A \| Agent_B \| Agent_C` |
|
|
319
|
+
| **Hierarchical** | `Agent_planner → Sequence( Agent_worker×N ) → synth` |
|
|
320
|
+
|
|
321
|
+
Same trick as Beat 1: instead of N libraries for N patterns, we found the M building blocks all N patterns are made of.
|
|
322
|
+
|
|
323
|
+
> 📖 Compare: [hand-rolled vs declarative](https://footprintjs.github.io/agentfootprint/getting-started/why/) · [migration from LangChain / CrewAI / LangGraph](https://footprintjs.github.io/agentfootprint/getting-started/vs/)
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 4. How do I see what my agent did?
|
|
328
|
+
|
|
329
|
+
Because we own the loop (Beat 2), every decision and execution is captured during traversal — not bolted on. The default capture is the **causal trace**: every stage, read, write, and decision evidence, as a JSON-portable, scrubbable, queryable, exportable artifact. Beyond the default, wire custom recorders for cost, latency, or quality scoring — any observation hook fires on the same stream.
|
|
330
|
+
|
|
331
|
+
<p align="center">
|
|
332
|
+
<picture>
|
|
333
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/causal-memory-dark.svg">
|
|
334
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/causal-memory-light.svg">
|
|
335
|
+
<img alt="agentfootprint causal memory — Each agent run produces a JSON-portable causal trace: a scrubbable timeline of every stage with reads, writes, and captured decision evidence. The trace card shows a time-travel slider (Step 5 of 17, Live), an execution timeline with stage-duration bars, and the captured decision evidence pill (riskTier eq high → reject). Two built-in lenses view it: Lens (agent-centric) and Explainable Trace (structural). Three programmatic consumers fan out from it: audit replay (GDPR Article 22 adverse-action notice answered from chain, no LLM call, $15/1M to $0.25/1M tokens), cheap-model triage (Sonnet trace fed to Haiku for follow-ups), and training data export (every chain is a labeled trajectory ready for SFT/DPO/process-RL). One recording, two lenses, three consumers, zero extra instrumentation. Powered by footprintjs causalChain()." src="docs/assets/causal-memory-light.svg" width="100%"/>
|
|
336
|
+
</picture>
|
|
337
|
+
</p>
|
|
338
|
+
|
|
339
|
+
The same trace serves three downstream consumers — no extra instrumentation:
|
|
340
|
+
|
|
341
|
+
1. **Audit / compliance.** Six months later, *"why was loan #42 rejected?"* answers from the chain (`creditScore=580 < 620 ∧ dti=0.6 > 0.43 → riskTier=high → REJECTED`). No LLM call. GDPR Art. 22, ECOA, and EU AI Act adverse-action notices write themselves from the captured decision evidence.
|
|
342
|
+
|
|
343
|
+
2. **Cheap-model triage.** A Sonnet trace becomes good *input* for Haiku to answer follow-ups. ~200 tokens at any model ($0.25/1M) vs ~2,500 tokens at a reasoning model ($15/1M). Memoization for agent thinking — no agent rerun.
|
|
344
|
+
|
|
345
|
+
3. **Training data export.** Every successful chain is a labeled trajectory — `causalMemory.exportForTraining({ format: 'sft' \| 'dpo' \| 'process-rl' })`. The chain provides per-step rewards out of the box, so process-RL is ready without a separate data-collection phase.
|
|
346
|
+
|
|
347
|
+
Two built-in lenses view the same trace:
|
|
348
|
+
|
|
349
|
+
| Lens | View | When to use |
|
|
350
|
+
|---|---|---|
|
|
351
|
+
| **Lens** | Agent-centric — User/Agent[3 slots]/Tool flowchart with iteration scrubber and round commentary | Live debugging, "what did Neo see at step 5?" |
|
|
352
|
+
| **Explainable Trace** | Structural — subflow tree, full flowchart, memory inspector, per-stage execution timeline | Architecture review, root-cause analysis |
|
|
353
|
+
|
|
354
|
+
> 📖 Powered by [footprintjs `causalChain()`](https://footprintjs.github.io/footPrint/blog/backward-causal-chain/) — backward thin-slicing on the commit log. [Causal memory deep dive](https://footprintjs.github.io/agentfootprint/causal-deep-dive/) · [Explainability & compliance](https://footprintjs.github.io/footPrint/blog/explainability-compliance/)
|
|
108
355
|
|
|
109
|
-
|
|
356
|
+
**One recording. Two lenses. Three consumers. Zero extra instrumentation.**
|
|
110
357
|
|
|
111
358
|
---
|
|
112
359
|
|
|
@@ -146,69 +393,6 @@ Swap `mock(...)` for `anthropic(...)` / `openai(...)` / `bedrock(...)` / `ollama
|
|
|
146
393
|
|
|
147
394
|
---
|
|
148
395
|
|
|
149
|
-
## A real agent in 8 lines
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
const agent = Agent.create({ provider, model: 'claude-sonnet-4-5-20250929' })
|
|
153
|
-
.system('You are a support assistant.')
|
|
154
|
-
.steering(toneRule) // always-on
|
|
155
|
-
.instruction(urgentRule) // rule-gated
|
|
156
|
-
.skill(billingSkill) // LLM-activated
|
|
157
|
-
.memory(conversationMemory) // cross-run, multi-tenant
|
|
158
|
-
.tool(weather)
|
|
159
|
-
.build();
|
|
160
|
-
|
|
161
|
-
await agent.run({ message: userInput, identity: { conversationId } });
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
The hand-rolled equivalent is ~80 lines of slot management, trigger evaluation, memory loading, and cache marker placement — and growing with every feature. The declarative version stays at 8.
|
|
165
|
-
|
|
166
|
-
> 📖 Compare: [hand-rolled vs declarative](https://footprintjs.github.io/agentfootprint/getting-started/why/) · [migration from LangChain / CrewAI / LangGraph](https://footprintjs.github.io/agentfootprint/getting-started/vs/)
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## The differentiator: the trace is a cache of the agent's thinking
|
|
171
|
-
|
|
172
|
-
Other agent frameworks remember *what was said*. agentfootprint's causal memory records the **decision evidence** — every value the flowchart captured during the run, persisted as a JSON-portable snapshot.
|
|
173
|
-
|
|
174
|
-
That changes the cost structure of everything that happens after the agent runs:
|
|
175
|
-
|
|
176
|
-
1. **Audit / explain** — six months later, "why was loan #42 rejected?" answers from the original evidence (creditScore=580, threshold=600), not reconstruction.
|
|
177
|
-
2. **Cheap-model triage** — a trace from Sonnet is good *input* for Haiku to answer follow-up questions about that run. Memoization for agent reasoning.
|
|
178
|
-
3. **Training data** — every successful production run is a labeled trajectory for SFT/DPO/process-RL, no separate data-collection phase.
|
|
179
|
-
|
|
180
|
-
One recording, three downstream consumers, no extra instrumentation.
|
|
181
|
-
|
|
182
|
-
> 📖 Deep dive: [Causal memory guide](https://footprintjs.github.io/agentfootprint/guides/causal-memory/)
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## What you can build
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
// Customer support — skills + memory + audit + cache
|
|
190
|
-
const agent = Agent.create({ provider, model })
|
|
191
|
-
.system('You are a friendly support assistant.')
|
|
192
|
-
.skill(billingSkill)
|
|
193
|
-
.steering(toneGuidelines)
|
|
194
|
-
.memory(conversationMemory)
|
|
195
|
-
.build();
|
|
196
|
-
|
|
197
|
-
// Research pipeline — multi-agent fan-out + merge
|
|
198
|
-
const research = Parallel.create()
|
|
199
|
-
.branch(optimist).branch(skeptic).branch(historian)
|
|
200
|
-
.merge(synthesizer)
|
|
201
|
-
.build();
|
|
202
|
-
|
|
203
|
-
// Streaming chat — token-by-token to a browser via SSE
|
|
204
|
-
agent.on('agentfootprint.stream.token', (e) => res.write(toSSE(e)));
|
|
205
|
-
await agent.run({ message: req.query.message });
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
> 📖 Full examples: [examples gallery](https://github.com/footprintjs/agentfootprint/tree/main/examples) · every example is also a CI test.
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
396
|
## Mocks first, production second
|
|
213
397
|
|
|
214
398
|
Build the entire app against in-memory mocks with **zero API cost**, then swap real infrastructure one boundary at a time.
|
|
@@ -226,19 +410,27 @@ The flowchart, recorders, and tests don't change between dev and prod.
|
|
|
226
410
|
|
|
227
411
|
## What ships today
|
|
228
412
|
|
|
229
|
-
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
-
|
|
236
|
-
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
-
|
|
413
|
+
**Core**
|
|
414
|
+
- 2 primitives — `LLMCall`, `Agent` (the ReAct loop)
|
|
415
|
+
- 4 control flows — `Sequence`, `Parallel`, `Conditional`, `Loop`
|
|
416
|
+
- One Injection primitive — `defineSkill` / `defineSteering` / `defineInstruction` / `defineFact`
|
|
417
|
+
|
|
418
|
+
**Adapters**
|
|
419
|
+
- 7 LLM providers — Anthropic · OpenAI · Bedrock · Ollama · Browser-Anthropic · Browser-OpenAI · Mock
|
|
420
|
+
- RAG · MCP · Memory store adapters — InMemory · Redis · AgentCore (Postgres / DynamoDB / Pinecone via lazy peer-deps)
|
|
421
|
+
|
|
422
|
+
**Operability**
|
|
423
|
+
- One Memory factory — 4 types × 7 strategies including **Causal**
|
|
424
|
+
- Provider-agnostic prompt caching — declarative per-injection, per-iteration marker recomputation
|
|
425
|
+
- Pause / resume — JSON-serializable checkpoints; resume hours later on a different server
|
|
426
|
+
- Resilience — `withRetry`, `withFallback`, `resilientProvider`
|
|
427
|
+
- 48+ typed observability events — context · stream · agent · cost · skill · permission · eval · memory · cache · embedding · error
|
|
428
|
+
|
|
429
|
+
**Tooling**
|
|
430
|
+
- **Lens** · **Explainable Trace** — two visual replays of the causal trace
|
|
431
|
+
- AI-coding-tool support — Claude Code · Cursor · Windsurf · Cline · Kiro · Copilot
|
|
240
432
|
|
|
241
|
-
> 📖 [
|
|
433
|
+
> 📖 [Agent API reference](https://footprintjs.github.io/agentfootprint/api/agent/) · [CHANGELOG](./CHANGELOG.md)
|
|
242
434
|
|
|
243
435
|
---
|
|
244
436
|
|
|
@@ -256,20 +448,6 @@ Roadmap items are *not* current API claims. If a feature isn't in `npm install a
|
|
|
256
448
|
|
|
257
449
|
---
|
|
258
450
|
|
|
259
|
-
## Design philosophy
|
|
260
|
-
|
|
261
|
-
Two principles shape the runtime:
|
|
262
|
-
|
|
263
|
-
**Connected data (Palantir, 2003).** Enterprise insight is bottlenecked by data fragmentation, not analyst skill. Agents face the same problem at runtime — disconnected tool state, lost decision evidence, scattered execution context. agentfootprint connects state, decisions, execution, and memory into one runtime footprint so the next iteration compounds the connection instead of paying for it again.
|
|
264
|
-
|
|
265
|
-
**Modular boundaries (Liskov, 1974).** Every framework boundary — `LLMProvider`, `ToolProvider`, `CacheStrategy`, `Recorder`, `MemoryStore` — is an LSP-substitutable interface. Swap implementations without changing agent code.
|
|
266
|
-
|
|
267
|
-
Connected data alone is fast but unmaintainable. Modular boundaries alone are clean but dumb. Together: a runtime that's both fast and reasonable.
|
|
268
|
-
|
|
269
|
-
> 📖 Long-form: [the Palantir lineage](https://footprintjs.github.io/agentfootprint/inspiration/connected-data/) · [the Liskov lineage](https://footprintjs.github.io/agentfootprint/inspiration/modularity/)
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
451
|
## Where to next
|
|
274
452
|
|
|
275
453
|
| If you are... | Go here |
|
|
@@ -277,8 +455,8 @@ Connected data alone is fast but unmaintainable. Modular boundaries alone are cl
|
|
|
277
455
|
| New to agents | [5-minute quick start](https://footprintjs.github.io/agentfootprint/getting-started/quick-start/) |
|
|
278
456
|
| Coming from LangChain / CrewAI / LangGraph | [Migration guide](https://footprintjs.github.io/agentfootprint/getting-started/vs/) |
|
|
279
457
|
| Architecting an enterprise rollout | [Production guide](https://footprintjs.github.io/agentfootprint/guides/deployment/) |
|
|
280
|
-
| Doing due diligence | [Architecture overview](https://footprintjs.github.io/agentfootprint/architecture/) |
|
|
281
|
-
| Researcher /
|
|
458
|
+
| Doing due diligence | [Architecture overview](https://footprintjs.github.io/agentfootprint/architecture/dependency-graph/) |
|
|
459
|
+
| Researcher / academic background | [Citations & prior art](https://footprintjs.github.io/agentfootprint/research/citations/) |
|
|
282
460
|
| Curious about design | [Inspiration docs](https://footprintjs.github.io/agentfootprint/inspiration/) |
|
|
283
461
|
|
|
284
462
|
Or jump into the [examples gallery](https://github.com/footprintjs/agentfootprint/tree/main/examples) — every example is also an end-to-end CI test.
|
|
@@ -287,7 +465,7 @@ Or jump into the [examples gallery](https://github.com/footprintjs/agentfootprin
|
|
|
287
465
|
|
|
288
466
|
## Built on
|
|
289
467
|
|
|
290
|
-
[footprintjs](https://github.com/footprintjs/footPrint) — the flowchart pattern for backend code.
|
|
468
|
+
[footprintjs](https://github.com/footprintjs/footPrint) — the flowchart pattern for backend code. agentfootprint's decision-evidence capture, narrative recording, and time-travel checkpointing are footprintjs primitives at the runtime layer.
|
|
291
469
|
|
|
292
470
|
You don't need to learn footprintjs to use agentfootprint — but if you want to build your own primitives at this depth, [start there](https://footprintjs.github.io/footPrint/).
|
|
293
471
|
|
package/dist/core/Agent.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.Agent = exports.AgentBuilder = void 0;
|
|
19
19
|
const footprintjs_1 = require("footprintjs");
|
|
20
|
+
const types_js_1 = require("../reliability/types.js");
|
|
20
21
|
const CacheDecisionSubflow_js_1 = require("../cache/CacheDecisionSubflow.js");
|
|
21
22
|
const CacheGateDecider_js_1 = require("../cache/CacheGateDecider.js");
|
|
22
23
|
const strategyRegistry_js_1 = require("../cache/strategyRegistry.js");
|
|
@@ -154,7 +155,14 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
154
155
|
* dispatch correctly when their visible-set changes mid-turn.
|
|
155
156
|
*/
|
|
156
157
|
externalToolProvider;
|
|
157
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Optional rules-based reliability config (v2.11.5+). Set via the
|
|
160
|
+
* builder's `.reliability({...})`. When present, every CallLLM
|
|
161
|
+
* execution is wrapped in a retry/fallback/fail-fast loop driven
|
|
162
|
+
* by `preCheck` and `postDecide` rules. Consumed by `buildCallLLMStage`.
|
|
163
|
+
*/
|
|
164
|
+
reliabilityConfig;
|
|
165
|
+
constructor(opts, systemPromptValue, registry, voice, injections = [], memories = [], outputSchemaParser, toolProvider, systemPromptCachePolicy = 'always', cachingDisabled = false, cacheStrategy, outputFallbackCfg, reliabilityConfig) {
|
|
158
166
|
super();
|
|
159
167
|
this.provider = opts.provider;
|
|
160
168
|
this.name = opts.name ?? 'Agent';
|
|
@@ -189,6 +197,8 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
189
197
|
this.costBudget = opts.costBudget;
|
|
190
198
|
if (opts.permissionChecker)
|
|
191
199
|
this.permissionChecker = opts.permissionChecker;
|
|
200
|
+
if (reliabilityConfig !== undefined)
|
|
201
|
+
this.reliabilityConfig = reliabilityConfig;
|
|
192
202
|
this.appName = voice.appName;
|
|
193
203
|
this.commentaryTemplates = voice.commentaryTemplates;
|
|
194
204
|
this.thinkingTemplates = voice.thinkingTemplates;
|
|
@@ -471,6 +481,38 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
471
481
|
const paused = this.detectPause(executor, result);
|
|
472
482
|
if (paused)
|
|
473
483
|
return paused;
|
|
484
|
+
// Reliability fail-fast translation (v2.11.5+) — when the
|
|
485
|
+
// reliability retry loop in callLLM hits a `fail-fast` decision,
|
|
486
|
+
// it writes scope.reliabilityFailKind + payload and calls $break.
|
|
487
|
+
// The chart stops; the executor returns the last finalContent
|
|
488
|
+
// (typically empty). At the API boundary we surface the typed
|
|
489
|
+
// error so consumers can `instanceof ReliabilityFailFastError`
|
|
490
|
+
// and branch on `.kind`.
|
|
491
|
+
if (this.reliabilityConfig !== undefined) {
|
|
492
|
+
const snap = executor.getSnapshot();
|
|
493
|
+
const state = snap.sharedState;
|
|
494
|
+
if (state.reliabilityFailKind !== undefined) {
|
|
495
|
+
// Reconstruct the cause Error from the captured message+name —
|
|
496
|
+
// see the matching note in reliabilityExecution.failFast about
|
|
497
|
+
// why we don't keep the original Error in scope.
|
|
498
|
+
let cause;
|
|
499
|
+
if (state.reliabilityFailCauseMessage !== undefined) {
|
|
500
|
+
cause = new Error(state.reliabilityFailCauseMessage);
|
|
501
|
+
if (state.reliabilityFailCauseName !== undefined) {
|
|
502
|
+
cause.name = state.reliabilityFailCauseName;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
throw new types_js_1.ReliabilityFailFastError({
|
|
506
|
+
kind: state.reliabilityFailKind,
|
|
507
|
+
reason: state.reliabilityFailReason ?? state.reliabilityFailKind,
|
|
508
|
+
...(cause !== undefined && { cause }),
|
|
509
|
+
...(state.reliabilityFailPayload !== undefined && {
|
|
510
|
+
payload: state.reliabilityFailPayload,
|
|
511
|
+
}),
|
|
512
|
+
snapshot: snap,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
474
516
|
if (result instanceof Error)
|
|
475
517
|
throw result;
|
|
476
518
|
if (typeof result === 'string')
|
|
@@ -558,6 +600,7 @@ class Agent extends RunnerBase_js_1.RunnerBase {
|
|
|
558
600
|
get toolSchemas() {
|
|
559
601
|
return toolSchemasResolved;
|
|
560
602
|
},
|
|
603
|
+
...(this.reliabilityConfig !== undefined && { reliability: this.reliabilityConfig }),
|
|
561
604
|
});
|
|
562
605
|
// routeDecider extracted to ./agent/stages/route.ts (v2.11.2).
|
|
563
606
|
const routeDecider = route_js_1.routeDeciderStage;
|