agentfootprint 7.28.0 → 8.1.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 (241) hide show
  1. package/AGENTS.md +23 -18
  2. package/README.md +41 -12
  3. package/ai-instructions/claude-code/SKILL.md +3 -3
  4. package/ai-instructions/clinerules +2 -2
  5. package/ai-instructions/copilot-instructions.md +2 -2
  6. package/ai-instructions/cursor/agentfootprint.md +2 -2
  7. package/ai-instructions/kiro/agentfootprint.md +2 -2
  8. package/ai-instructions/windsurfrules +2 -2
  9. package/dist/adapters/llm/OllamaProvider.js +628 -0
  10. package/dist/adapters/llm/OllamaProvider.js.map +1 -0
  11. package/dist/adapters/llm/OpenAIProvider.js +12 -21
  12. package/dist/adapters/llm/OpenAIProvider.js.map +1 -1
  13. package/dist/adapters/llm/createProvider.js +35 -6
  14. package/dist/adapters/llm/createProvider.js.map +1 -1
  15. package/dist/core/agent/AgentBuilder.js +45 -11
  16. package/dist/core/agent/AgentBuilder.js.map +1 -1
  17. package/dist/core/agent/watch.js +39 -0
  18. package/dist/core/agent/watch.js.map +1 -0
  19. package/dist/core/agent/window/errors.js +4 -3
  20. package/dist/core/agent/window/errors.js.map +1 -1
  21. package/dist/core/agent/window/strategies/slidingWindow.js +2 -2
  22. package/dist/debug/finders.js +4 -0
  23. package/dist/debug/finders.js.map +1 -1
  24. package/dist/debug.js +4 -0
  25. package/dist/debug.js.map +1 -1
  26. package/dist/doors/context.js +34 -0
  27. package/dist/doors/context.js.map +1 -0
  28. package/dist/doors/hosting.js +32 -0
  29. package/dist/doors/hosting.js.map +1 -0
  30. package/dist/doors/memory.js +36 -0
  31. package/dist/doors/memory.js.map +1 -0
  32. package/dist/doors/observe.js +52 -0
  33. package/dist/doors/observe.js.map +1 -0
  34. package/dist/doors/providers.js +43 -0
  35. package/dist/doors/providers.js.map +1 -0
  36. package/dist/doors/resilience.js +61 -0
  37. package/dist/doors/resilience.js.map +1 -0
  38. package/dist/doors/security.js +39 -0
  39. package/dist/doors/security.js.map +1 -0
  40. package/dist/embedders/index.js.map +1 -1
  41. package/dist/esm/adapters/llm/OllamaProvider.d.ts +182 -0
  42. package/dist/esm/adapters/llm/OllamaProvider.js +622 -0
  43. package/dist/esm/adapters/llm/OllamaProvider.js.map +1 -0
  44. package/dist/esm/adapters/llm/OpenAIProvider.d.ts +0 -12
  45. package/dist/esm/adapters/llm/OpenAIProvider.js +11 -19
  46. package/dist/esm/adapters/llm/OpenAIProvider.js.map +1 -1
  47. package/dist/esm/adapters/llm/createProvider.d.ts +25 -8
  48. package/dist/esm/adapters/llm/createProvider.js +35 -6
  49. package/dist/esm/adapters/llm/createProvider.js.map +1 -1
  50. package/dist/esm/core/agent/AgentBuilder.d.ts +37 -7
  51. package/dist/esm/core/agent/AgentBuilder.js +45 -11
  52. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  53. package/dist/esm/core/agent/watch.d.ts +59 -0
  54. package/dist/esm/core/agent/watch.js +38 -0
  55. package/dist/esm/core/agent/watch.js.map +1 -0
  56. package/dist/esm/core/agent/window/errors.js +4 -3
  57. package/dist/esm/core/agent/window/errors.js.map +1 -1
  58. package/dist/esm/core/agent/window/strategies/slidingWindow.d.ts +2 -2
  59. package/dist/esm/core/agent/window/strategies/slidingWindow.js +2 -2
  60. package/dist/esm/debug/finders.d.ts +4 -0
  61. package/dist/esm/debug/finders.js +4 -0
  62. package/dist/esm/debug/finders.js.map +1 -1
  63. package/dist/esm/debug.d.ts +4 -0
  64. package/dist/esm/debug.js +4 -0
  65. package/dist/esm/debug.js.map +1 -1
  66. package/dist/esm/doors/context.d.ts +17 -0
  67. package/dist/esm/doors/context.js +18 -0
  68. package/dist/esm/doors/context.js.map +1 -0
  69. package/dist/esm/doors/hosting.d.ts +15 -0
  70. package/dist/esm/doors/hosting.js +16 -0
  71. package/dist/esm/doors/hosting.js.map +1 -0
  72. package/dist/esm/doors/memory.d.ts +19 -0
  73. package/dist/esm/doors/memory.js +20 -0
  74. package/dist/esm/doors/memory.js.map +1 -0
  75. package/dist/esm/doors/observe.d.ts +35 -0
  76. package/dist/esm/doors/observe.js +36 -0
  77. package/dist/esm/doors/observe.js.map +1 -0
  78. package/dist/esm/doors/providers.d.ts +26 -0
  79. package/dist/esm/doors/providers.js +27 -0
  80. package/dist/esm/doors/providers.js.map +1 -0
  81. package/dist/esm/doors/resilience.d.ts +37 -0
  82. package/dist/esm/doors/resilience.js +39 -0
  83. package/dist/esm/doors/resilience.js.map +1 -0
  84. package/dist/esm/doors/security.d.ts +22 -0
  85. package/dist/esm/doors/security.js +23 -0
  86. package/dist/esm/doors/security.js.map +1 -0
  87. package/dist/esm/embedders/index.d.ts +4 -0
  88. package/dist/esm/embedders/index.js.map +1 -1
  89. package/dist/esm/hosting-providers.d.ts +4 -0
  90. package/dist/esm/hosting-providers.js +4 -0
  91. package/dist/esm/hosting-providers.js.map +1 -1
  92. package/dist/esm/identity.d.ts +4 -0
  93. package/dist/esm/identity.js +4 -0
  94. package/dist/esm/identity.js.map +1 -1
  95. package/dist/esm/index.d.ts +1 -0
  96. package/dist/esm/index.js.map +1 -1
  97. package/dist/esm/injection-engine.d.ts +4 -0
  98. package/dist/esm/injection-engine.js +4 -0
  99. package/dist/esm/injection-engine.js.map +1 -1
  100. package/dist/esm/llm-providers.d.ts +4 -0
  101. package/dist/esm/llm-providers.js +4 -0
  102. package/dist/esm/llm-providers.js.map +1 -1
  103. package/dist/esm/locales/index.d.ts +4 -0
  104. package/dist/esm/locales/index.js +4 -0
  105. package/dist/esm/locales/index.js.map +1 -1
  106. package/dist/esm/memory-providers.d.ts +4 -0
  107. package/dist/esm/memory-providers.js +4 -0
  108. package/dist/esm/memory-providers.js.map +1 -1
  109. package/dist/esm/observability/contextError/finders/index.d.ts +4 -0
  110. package/dist/esm/observability/contextError/finders/index.js.map +1 -1
  111. package/dist/esm/observability-providers.d.ts +4 -0
  112. package/dist/esm/observability-providers.js +4 -0
  113. package/dist/esm/observability-providers.js.map +1 -1
  114. package/dist/esm/providers.d.ts +13 -5
  115. package/dist/esm/providers.js +13 -5
  116. package/dist/esm/providers.js.map +1 -1
  117. package/dist/esm/reliability/index.d.ts +8 -0
  118. package/dist/esm/reliability/index.js +8 -0
  119. package/dist/esm/reliability/index.js.map +1 -1
  120. package/dist/esm/status.d.ts +4 -0
  121. package/dist/esm/status.js +4 -0
  122. package/dist/esm/status.js.map +1 -1
  123. package/dist/esm/strategies/index.d.ts +4 -0
  124. package/dist/esm/strategies/index.js +4 -0
  125. package/dist/esm/strategies/index.js.map +1 -1
  126. package/dist/esm/stream.d.ts +4 -0
  127. package/dist/esm/stream.js +4 -0
  128. package/dist/esm/stream.js.map +1 -1
  129. package/dist/esm/thinking/OllamaThinkingHandler.d.ts +60 -0
  130. package/dist/esm/thinking/OllamaThinkingHandler.js +107 -0
  131. package/dist/esm/thinking/OllamaThinkingHandler.js.map +1 -0
  132. package/dist/esm/thinking/index.d.ts +5 -0
  133. package/dist/esm/thinking/index.js +5 -0
  134. package/dist/esm/thinking/index.js.map +1 -1
  135. package/dist/esm/thinking/registry.js +2 -0
  136. package/dist/esm/thinking/registry.js.map +1 -1
  137. package/dist/esm/tool-providers/index.d.ts +4 -0
  138. package/dist/esm/tool-providers/index.js +4 -0
  139. package/dist/esm/tool-providers/index.js.map +1 -1
  140. package/dist/hosting-providers.js +4 -0
  141. package/dist/hosting-providers.js.map +1 -1
  142. package/dist/identity.js +4 -0
  143. package/dist/identity.js.map +1 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/injection-engine.js +4 -0
  146. package/dist/injection-engine.js.map +1 -1
  147. package/dist/llm-providers.js +4 -0
  148. package/dist/llm-providers.js.map +1 -1
  149. package/dist/locales/index.js +4 -0
  150. package/dist/locales/index.js.map +1 -1
  151. package/dist/memory-providers.js +4 -0
  152. package/dist/memory-providers.js.map +1 -1
  153. package/dist/observability/contextError/finders/index.js.map +1 -1
  154. package/dist/observability-providers.js +4 -0
  155. package/dist/observability-providers.js.map +1 -1
  156. package/dist/providers.js +16 -6
  157. package/dist/providers.js.map +1 -1
  158. package/dist/reliability/index.js +8 -0
  159. package/dist/reliability/index.js.map +1 -1
  160. package/dist/status.js +4 -0
  161. package/dist/status.js.map +1 -1
  162. package/dist/strategies/index.js +4 -0
  163. package/dist/strategies/index.js.map +1 -1
  164. package/dist/stream.js +4 -0
  165. package/dist/stream.js.map +1 -1
  166. package/dist/thinking/OllamaThinkingHandler.js +111 -0
  167. package/dist/thinking/OllamaThinkingHandler.js.map +1 -0
  168. package/dist/thinking/index.js +8 -1
  169. package/dist/thinking/index.js.map +1 -1
  170. package/dist/thinking/registry.js +2 -0
  171. package/dist/thinking/registry.js.map +1 -1
  172. package/dist/tool-providers/index.js +4 -0
  173. package/dist/tool-providers/index.js.map +1 -1
  174. package/dist/types/adapters/llm/OllamaProvider.d.ts +183 -0
  175. package/dist/types/adapters/llm/OllamaProvider.d.ts.map +1 -0
  176. package/dist/types/adapters/llm/OpenAIProvider.d.ts +0 -12
  177. package/dist/types/adapters/llm/OpenAIProvider.d.ts.map +1 -1
  178. package/dist/types/adapters/llm/createProvider.d.ts +25 -8
  179. package/dist/types/adapters/llm/createProvider.d.ts.map +1 -1
  180. package/dist/types/core/agent/AgentBuilder.d.ts +37 -7
  181. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  182. package/dist/types/core/agent/watch.d.ts +60 -0
  183. package/dist/types/core/agent/watch.d.ts.map +1 -0
  184. package/dist/types/core/agent/window/errors.d.ts.map +1 -1
  185. package/dist/types/core/agent/window/strategies/slidingWindow.d.ts +2 -2
  186. package/dist/types/debug/finders.d.ts +4 -0
  187. package/dist/types/debug/finders.d.ts.map +1 -1
  188. package/dist/types/debug.d.ts +4 -0
  189. package/dist/types/debug.d.ts.map +1 -1
  190. package/dist/types/doors/context.d.ts +18 -0
  191. package/dist/types/doors/context.d.ts.map +1 -0
  192. package/dist/types/doors/hosting.d.ts +16 -0
  193. package/dist/types/doors/hosting.d.ts.map +1 -0
  194. package/dist/types/doors/memory.d.ts +20 -0
  195. package/dist/types/doors/memory.d.ts.map +1 -0
  196. package/dist/types/doors/observe.d.ts +36 -0
  197. package/dist/types/doors/observe.d.ts.map +1 -0
  198. package/dist/types/doors/providers.d.ts +27 -0
  199. package/dist/types/doors/providers.d.ts.map +1 -0
  200. package/dist/types/doors/resilience.d.ts +38 -0
  201. package/dist/types/doors/resilience.d.ts.map +1 -0
  202. package/dist/types/doors/security.d.ts +23 -0
  203. package/dist/types/doors/security.d.ts.map +1 -0
  204. package/dist/types/embedders/index.d.ts +4 -0
  205. package/dist/types/embedders/index.d.ts.map +1 -1
  206. package/dist/types/hosting-providers.d.ts +4 -0
  207. package/dist/types/hosting-providers.d.ts.map +1 -1
  208. package/dist/types/identity.d.ts +4 -0
  209. package/dist/types/identity.d.ts.map +1 -1
  210. package/dist/types/index.d.ts +1 -0
  211. package/dist/types/index.d.ts.map +1 -1
  212. package/dist/types/injection-engine.d.ts +4 -0
  213. package/dist/types/injection-engine.d.ts.map +1 -1
  214. package/dist/types/llm-providers.d.ts +4 -0
  215. package/dist/types/llm-providers.d.ts.map +1 -1
  216. package/dist/types/locales/index.d.ts +4 -0
  217. package/dist/types/locales/index.d.ts.map +1 -1
  218. package/dist/types/memory-providers.d.ts +4 -0
  219. package/dist/types/memory-providers.d.ts.map +1 -1
  220. package/dist/types/observability/contextError/finders/index.d.ts +4 -0
  221. package/dist/types/observability/contextError/finders/index.d.ts.map +1 -1
  222. package/dist/types/observability-providers.d.ts +4 -0
  223. package/dist/types/observability-providers.d.ts.map +1 -1
  224. package/dist/types/providers.d.ts +13 -5
  225. package/dist/types/providers.d.ts.map +1 -1
  226. package/dist/types/reliability/index.d.ts +8 -0
  227. package/dist/types/reliability/index.d.ts.map +1 -1
  228. package/dist/types/status.d.ts +4 -0
  229. package/dist/types/status.d.ts.map +1 -1
  230. package/dist/types/strategies/index.d.ts +4 -0
  231. package/dist/types/strategies/index.d.ts.map +1 -1
  232. package/dist/types/stream.d.ts +4 -0
  233. package/dist/types/stream.d.ts.map +1 -1
  234. package/dist/types/thinking/OllamaThinkingHandler.d.ts +61 -0
  235. package/dist/types/thinking/OllamaThinkingHandler.d.ts.map +1 -0
  236. package/dist/types/thinking/index.d.ts +5 -0
  237. package/dist/types/thinking/index.d.ts.map +1 -1
  238. package/dist/types/thinking/registry.d.ts.map +1 -1
  239. package/dist/types/tool-providers/index.d.ts +4 -0
  240. package/dist/types/tool-providers/index.d.ts.map +1 -1
  241. package/package.json +184 -155
package/AGENTS.md CHANGED
@@ -56,9 +56,9 @@ await agent.run({ message: 'How long does a refund take?' });
56
56
 
57
57
  | Boundary | Mock for development | Production swap |
58
58
  |---|---|---|
59
- | LLM provider | `mock({ reply })` · `mock({ replies })` for scripted ReAct | `anthropic()` · `openai()` · `bedrock()` · `ollama()` |
59
+ | LLM provider | `mock({ reply })` · `mock({ replies })` for scripted ReAct | `ollama('<model>')` — a real model, still $0 and no key · then `anthropic()` · `openai()` · `bedrock()` |
60
60
  | Embedder | `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
61
- | Memory store | `InMemoryStore` | `RedisStore` (`agentfootprint/memory-redis`) · `AgentCoreStore` (`agentfootprint/memory-agentcore`) · DynamoDB / Postgres / Pinecone (planned) |
61
+ | Memory store | `InMemoryStore` | `RedisStore` (`agentfootprint/memory`) · `AgentCoreStore` (`agentfootprint/memory`) · DynamoDB / Postgres / Pinecone (planned) |
62
62
  | MCP server | `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` to a real server |
63
63
  | Tool execute | inline `async () => '...'` closure | real implementation |
64
64
 
@@ -67,8 +67,8 @@ When generating starter code for users, **default to the mock surface** unless t
67
67
  **Subpath imports** for memory adapters keep the main barrel small + tree-shaking clean:
68
68
 
69
69
  ```typescript
70
- import { RedisStore } from 'agentfootprint/memory-redis';
71
- import { AgentCoreStore } from 'agentfootprint/memory-agentcore';
70
+ import { RedisStore } from 'agentfootprint/memory';
71
+ import { AgentCoreStore } from 'agentfootprint/memory';
72
72
  ```
73
73
 
74
74
  Both lazy-require their SDK (`ioredis` / `@aws-sdk/client-bedrock-agent-runtime`) and accept `_client` for test injection.
@@ -152,7 +152,7 @@ agent.rag(docs);
152
152
 
153
153
  ```typescript
154
154
  import { Agent, defineTool } from 'agentfootprint';
155
- import { anthropic } from 'agentfootprint/llm-providers';
155
+ import { anthropic } from 'agentfootprint/providers';
156
156
 
157
157
  const agent = Agent.create({
158
158
  provider: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }),
@@ -177,7 +177,7 @@ Builder methods:
177
177
 
178
178
  ```typescript
179
179
  import { LLMCall } from 'agentfootprint';
180
- import { anthropic } from 'agentfootprint/llm-providers';
180
+ import { anthropic } from 'agentfootprint/providers';
181
181
 
182
182
  const call = LLMCall.create({ provider: anthropic(...), model: 'claude-sonnet-4-5-20250929' })
183
183
  .system('You are a terse assistant.')
@@ -401,21 +401,26 @@ Browse [`examples/patterns/`](examples/patterns/) — every pattern is a runnabl
401
401
 
402
402
  ```typescript
403
403
  import { mock } from 'agentfootprint';
404
- // Vendor-SDK providers (lazy peer-deps) live on the dedicated subpath:
405
- import { anthropic, openai, bedrock, ollama } from 'agentfootprint/llm-providers';
404
+ import { anthropic, openai, bedrock, ollama } from 'agentfootprint/providers';
406
405
 
407
- // Adapter-swap testing: same agent, different provider, $0 in CI
406
+ // The ladder: shape the logic on the mock → check it against a REAL model for
407
+ // free → pay only in production. One argument changes; the agent does not.
408
408
  const provider = process.env.NODE_ENV === 'production'
409
409
  ? anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! })
410
- : mock({ reply: 'test response' });
410
+ : ollama('llama3.2'); // or mock({ reply: 'test response' }) for determinism
411
411
  ```
412
412
 
413
413
  Every provider implements the same `LLMProvider` interface. `mock`,
414
414
  `browserAnthropic`, `browserOpenai`, and `createProvider` ship on the main
415
- barrel; the vendor-SDK-backed providers (`anthropic` · `openai` · `bedrock`
416
- · `ollama`) live ONLY at `agentfootprint/llm-providers` (legacy alias:
417
- `agentfootprint/providers`) so bundlers never walk their lazy peer-dep
418
- requires. Browser variants exist for client-side use.
415
+ barrel; `anthropic` · `openai` · `bedrock` · `ollama` live ONLY at
416
+ `agentfootprint/providers` so bundlers never walk the vendor-SDK requires.
417
+ Browser variants exist for client-side use.
418
+
419
+ `ollama('<model>')` is the middle rung and needs NO vendor SDK — it talks
420
+ Ollama's native API over `fetch`. Install Ollama, `ollama pull llama3.2`, done.
421
+ When it can't work it says so in words that contain the fix (`ollama serve`,
422
+ `ollama pull <model>`), never a raw connection error. `OLLAMA_MODEL=<model>`
423
+ makes `providerFromEnv()` pick it, ahead of the cloud credentials.
419
424
 
420
425
  ### Pause / Resume (Human-in-the-Loop)
421
426
 
@@ -444,11 +449,11 @@ if (isPaused(result)) {
444
449
 
445
450
  ```typescript
446
451
  import { withRetry, withFallback, fallbackProvider, withCircuitBreaker } from 'agentfootprint/resilience';
447
- import { anthropic, openai, ollama } from 'agentfootprint/llm-providers';
452
+ import { anthropic, openai, ollama } from 'agentfootprint/providers';
448
453
 
449
454
  const reliable = withRetry(provider, { maxAttempts: 3 });
450
455
  const resilient = withFallback(primary, fallback);
451
- const chain = fallbackProvider(anthropic({...}), openai({...}), ollama({...}));
456
+ const chain = fallbackProvider(anthropic({...}), openai({...}), ollama('llama3.2'));
452
457
  const guarded = withCircuitBreaker(provider);
453
458
  ```
454
459
 
@@ -478,7 +483,7 @@ Recorders (auto-attached when relevant builder method is called):
478
483
  - `evalRecorder` · `memoryRecorder` · `skillRecorder`
479
484
 
480
485
  **Observer delivery tier (RFC-001 Block 10):** `Agent.create({ observerDelivery:
481
- 'deferred' })` routes the bridge recorders above + consumer `.recorder()` /
486
+ 'deferred' })` routes the bridge recorders above + consumer `.watch()` /
482
487
  `agent.attach()` recorders through footprintjs's bounded capture queue —
483
488
  capture inline (≈ µs), deliver one beat behind, drain synchronously at run
484
489
  resolve / reject / pause. Default `'inline'` = byte-identical attach path, no
@@ -502,7 +507,7 @@ One builder call mounts ONE skill that, when the user asks a why-question, unloc
502
507
 
503
508
  - ❌ **Don't ship a `ReflexionAgent` class.** Compose `Sequence(Agent, critique-LLM, Agent)`.
504
509
  - ❌ **Don't use `agent.run('string')`** — use `agent.run({ message: '...', identity? })`.
505
- - ❌ **Don't import from non-existent subpaths** like `'agentfootprint/instructions'` — the injection factories live on the main barrel (or `'agentfootprint/injection-engine'`). NOTE: `'agentfootprint/observe'`, `'agentfootprint/security'`, `'agentfootprint/resilience'`, `'agentfootprint/llm-providers'`, `'agentfootprint/memory'`, `'agentfootprint/tool-providers'`, `'agentfootprint/locales'` ARE real subpaths — some surfaces (vendor providers, resilience decorators) live ONLY there, not on the main barrel.
510
+ - ❌ **Don't import from non-existent subpaths** like `'agentfootprint/instructions'` — the injection factories live on the main barrel (or `'agentfootprint/context'`). NOTE: `'agentfootprint/observe'`, `'agentfootprint/security'`, `'agentfootprint/resilience'`, `'agentfootprint/providers'`, `'agentfootprint/memory'`, `'agentfootprint/providers'`, `'agentfootprint/observe'` ARE real subpaths — some surfaces (vendor providers, resilience decorators) live ONLY there, not on the main barrel.
506
511
  - ❌ **Don't use `.memoryPipeline(pipeline)`** — that's the v1 API. Use `.memory(defineMemory({...}))`.
507
512
  - ❌ **Don't fall back when TopK threshold returns nothing.** Strict semantics: garbage past context > none is wrong.
508
513
  - ❌ **Don't store closures or class instances in scope** — TransactionBuffer can't clone functions. Memory-store entries serialize to JSON.
package/README.md CHANGED
@@ -102,21 +102,25 @@ const result = await agent.run({ message: 'Weather in Paris?' });
102
102
  console.log(result); // → "I checked: it is 72°F and sunny."
103
103
  ```
104
104
 
105
- For production, import a real provider from `agentfootprint/llm-providers` and swap it in — `anthropic(...)` / `openai(...)` / `bedrock(...)` / `ollama(...)`. Only the import line changes; the agent code stays the same. (The vendor-SDK providers live on the `agentfootprint/llm-providers` subpath so the main `agentfootprint` barrel stays free of optional peer-dep requires; `mock`, `browserAnthropic`, and `browserOpenai` are on the main barrel.)
105
+ For production, import a real provider from `agentfootprint/providers` and swap it in — `anthropic(...)` / `openai(...)` / `bedrock(...)` / `ollama(...)`. Only the import line changes; the agent code stays the same. (The vendor-SDK providers live on the `agentfootprint/providers` subpath so the main `agentfootprint` barrel stays free of optional peer-dep requires; `mock`, `browserAnthropic`, and `browserOpenai` are on the main barrel.)
106
106
 
107
- ### Run against a local model (Ollama, llama.cpp, vLLM any OpenAI-compatible endpoint)
107
+ ### Run against a local model the free rung between the mock and the bill
108
108
 
109
- No cloud account, no API key, $0 per token. `openai({ baseURL })` targets any server that speaks the OpenAI Chat Completions API — Ollama has its own one-line shortcut:
109
+ No cloud account, no API key, no vendor SDK, $0 per token:
110
110
 
111
111
  ```typescript
112
112
  import { Agent } from 'agentfootprint';
113
- import { ollama } from 'agentfootprint/llm-providers';
113
+ import { ollama } from 'agentfootprint/providers';
114
114
 
115
- const agent = Agent.create({ provider: ollama({ defaultModel: 'llama3.1' }), model: 'llama3.1' }).build();
116
- // → talks to http://localhost:11434/v1 (run `ollama pull llama3.1` first)
115
+ const agent = Agent.create({ provider: ollama('llama3.2'), model: 'llama3.2' }).build();
116
+ // → talks to http://localhost:11434 (run `ollama pull llama3.2` first)
117
117
  ```
118
118
 
119
- For llama.cpp's `llama-server` or vLLM, swap in `openai({ baseURL: 'http://localhost:8080/v1', apiKey: 'not-needed', defaultModel: '…' })` same `Agent` code either way. Local servers usually ignore `apiKey`, and most only accept the older `max_tokens` field; both are handled automatically whenever `baseURL` is set. Full recipes: [OpenAI-compatible endpoints](https://footprintjs.github.io/agentfootprint/docs/build/openai/#openai-compatible-endpoints-ollama-llamacpp-vllm-together-groq-lm-studio) · [Ollama guide](https://footprintjs.github.io/agentfootprint/docs/build/ollama/).
119
+ This is the step that makes "the test run and the production run are the same code path" more than a slogan. A mock proves your control flow; it can't tell you whether a real model calls your tool, or what it makes of a tool description you wrote in a hurry. A local model can — and because it's free, you'll actually check before you pay.
120
+
121
+ `ollama()` talks Ollama's native API directly, so there's nothing to install on this side, streamed calls report real token counts (so `.compaction()` and cost budgets work), and when it can't work it says why in words that contain the fix — `ollama serve` when nothing is listening, `ollama pull <model>` when the model isn't there, never a raw connection error and never a hang.
122
+
123
+ For llama.cpp's `llama-server`, vLLM, Together or Groq, use `openai({ baseURL: 'http://localhost:8080/v1', apiKey: 'not-needed', defaultModel: '…' })` — any server speaking the OpenAI Chat Completions API, same `Agent` code either way. Full recipes: [Ollama guide](https://footprintjs.github.io/agentfootprint/docs/build/ollama/) · [OpenAI-compatible endpoints](https://footprintjs.github.io/agentfootprint/docs/build/openai/#openai-compatible-endpoints-ollama-llamacpp-vllm-together-groq-lm-studio).
120
124
 
121
125
  ### Then add context
122
126
 
@@ -309,7 +313,7 @@ source, and returns the same honest result `rerunWithoutSources` gives you. `for
309
313
  Branch, never rewrite.
310
314
 
311
315
  ```ts
312
- import { recordedChat } from 'agentfootprint/debug';
316
+ import { recordedChat } from 'agentfootprint/observe';
313
317
 
314
318
  const chat = recordedChat({ makeAgent }); // your factory, specs applied at construction
315
319
  await chat.send('Should we BUY or HOLD?'); // recorded turn (frozen evidence)
@@ -624,6 +628,30 @@ npm run example examples/features/38-act.ts
624
628
 
625
629
  See [The moments of the loop](https://footprintjs.github.io/agentfootprint/docs/build/loop-moments/).
626
630
 
631
+ ### Watch — who is looking while it does
632
+
633
+ `.act()` says what the agent may do. `.watch()` says who is looking while it does it. Observers handed to the builder are attached before `build()` returns, so there is no window where the agent has run and nobody was watching:
634
+
635
+ ```ts
636
+ const routes = routeRecorder();
637
+ const choices = toolChoiceRecorder({ embedder: staticEmbedder() });
638
+
639
+ const agent = Agent.create({ provider, model })
640
+ .watch(routes, choices) // build-time — sees the very first run
641
+ .act({ beforeTool: [refundCeiling] })
642
+ .build();
643
+
644
+ await agent.run({ message: 'refund order 4471' });
645
+
646
+ console.log(await choices.getFlagged()); // calls where the tool choice was a near-tie
647
+ ```
648
+
649
+ Variadic, because observers come in sets. It returns the builder — the runtime door is still `agent.attach(observer)`, which returns an `Unsubscribe` you own and call when the observer's life ends. Same mechanism underneath, so mixing them is fine and order is preserved.
650
+
651
+ There is deliberately no list of "watch moments" to go with `.act()`'s five. A rule has to be *told* where it may speak, so that list is closed and compiler-pinned; an observer attends the whole stream, and any list we published would be a vocabulary we then had to keep true.
652
+
653
+ *(`.recorder()` is the same door under its old, internals-flavoured name. It still works and is deprecated — see [8.0.0](./CHANGELOG.md).)*
654
+
627
655
  ---
628
656
 
629
657
  ## 🐛 Debug — see what your agent did
@@ -743,7 +771,7 @@ map; you decide.
743
771
  Answering *"why was the loan rejected?"* from captured evidence is the [debug door above](#-debug--see-what-your-agent-did). The audit door adds the integrity layer: prove the **record itself** hasn't been edited since capture. `auditExport()` hash-chains every typed event — decisions, tool calls, validation rejections, permission verdicts, costs — into an append-only bundle (EU AI Act Art. 12 record-keeping shape); `verifyAuditBundle()` re-checks it **offline** — no agent, no LLM — and names the exact record any tamper broke.
744
772
 
745
773
  ```ts
746
- import { auditExport, verifyAuditBundle } from 'agentfootprint/observability-providers';
774
+ import { auditExport, verifyAuditBundle } from 'agentfootprint/observe';
747
775
 
748
776
  const audit = auditExport({ agent: 'ledger-auditor' });
749
777
  const stop = agent.enable.observability({ strategy: audit });
@@ -769,7 +797,7 @@ Build the entire app against in-memory mocks with **zero API cost**, then swap r
769
797
 
770
798
  | Boundary | Dev | Prod |
771
799
  |---|---|---|
772
- | LLM provider | `mock(...)` | `anthropic()` · `openai()` · `bedrock()` · `ollama()` |
800
+ | LLM provider | `mock(...)` | `ollama('<model>')` free · `anthropic()` · `openai()` · `bedrock()` |
773
801
  | Memory store | `InMemoryStore` | `RedisStore` · `AgentCoreStore` |
774
802
  | MCP | `mockMcpClient(...)` | `mcpClient({ transport })` |
775
803
  | Cache strategy | `NoOpCacheStrategy` | auto-selected per provider |
@@ -799,7 +827,7 @@ The flowchart, recorders, and tests don't change between dev and prod.
799
827
  | `anthropic` | Claude (Sonnet, Opus, Haiku) via `@anthropic-ai/sdk` |
800
828
  | `openai` | GPT-4o, GPT-4-turbo via `openai` SDK |
801
829
  | `bedrock` | Claude / Titan / Mistral via AWS Bedrock runtime |
802
- | `ollama` | Local models via Ollama · `openai({ baseURL })` also reaches llama.cpp, vLLM, and any other OpenAI-compatible endpoint |
830
+ | `ollama` | Local models, over Ollama's native API — no SDK, no key, real token counts, refusals that name `ollama serve` / `ollama pull` · `openai({ baseURL })` reaches llama.cpp, vLLM, and any other OpenAI-compatible endpoint |
803
831
  | `browserAnthropic` | Browser-side Claude calls (no proxy server) |
804
832
  | `browserOpenai` | Browser-side OpenAI calls (no proxy server) |
805
833
  | `mock` | Deterministic dev/test (zero API cost) |
@@ -855,7 +883,8 @@ Import one thing, ship one thing. agentfootprint is built so your bundle grows o
855
883
 
856
884
  - **Dual build, true ESM.** Ships CommonJS (`require`) **and** real ECMAScript Modules (`import`) with TypeScript types. The ESM build is `type:module` with explicit `.js` import extensions, so it loads as true ESM under Node, Vite, Next, Deno, and Bun — no shims.
857
885
  - **Per-file modules + honest `sideEffects`.** The dist is emitted file-by-file (never pre-bundled), so bundlers drop every export you don't touch. A small `import { defineTool }` doesn't pull in the Agent runtime, injection engine, memory stores, or LLM providers.
858
- - **Subpath exports + lazy peer-deps.** Heavyweight integrations live behind their own subpaths and load their SDK **only when you instantiate them** importing agentfootprint never bundles `@anthropic-ai/sdk`, `ioredis`, the AWS SDKs, or the MCP SDK unless you actually use that adapter.
886
+ - **Ten doors, named for what you're doing.** `agentfootprint` · `/providers` (plug in a backend) · `/memory` (state that outlives a turn) · `/observe` (everything that watches) · `/context` (how context gets assembled) · `/resilience` (when the call fails) · `/security` (who may do what) · `/hosting` (behind a wire) · `/events` (the typed wire vocabulary) · `/cache` (prompt caching). 8.0.0 consolidated 26 internals-named subpaths into these; every old path still resolves for all of 8.x.
887
+ - **Lazy peer-deps.** Heavyweight integrations load their SDK **only when you instantiate them** — importing agentfootprint never bundles `@anthropic-ai/sdk`, `ioredis`, the AWS SDKs, or the MCP SDK unless you actually use that adapter.
859
888
 
860
889
  **Proven, not promised.** A CI smoke test bundles a minimal `import { defineTool }` and asserts the Agent runtime, injection engine, memory stores, and providers are pruned; a second test loads the main barrel and every subpath as true ESM and verifies the lazy-adapter loader works under ESM (`createRequire`, not a bare `require`). See [`test/esm-packaging.test.ts`](test/esm-packaging.test.ts).
861
890
 
@@ -74,8 +74,8 @@ Build the entire agent + context engineering + tools + memory + RAG + MCP with i
74
74
 
75
75
  | Mock | Production swap |
76
76
  |---|---|
77
- | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `anthropic()` / `openai()` / `bedrock()` / `ollama()` |
78
- | `InMemoryStore` | `RedisStore` (`agentfootprint/memory-redis`) · `AgentCoreStore` (`agentfootprint/memory-agentcore`) · Dynamo · Postgres · Pinecone (planned) |
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
79
  | `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
80
80
  | `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` real server |
81
81
  | inline `defineTool({ execute: async () => '...' })` | real implementation |
@@ -253,7 +253,7 @@ The 7 **strategies**:
253
253
 
254
254
  ```typescript
255
255
  import { Agent } from 'agentfootprint';
256
- import { mcpClient } from 'agentfootprint/tool-providers';
256
+ import { mcpClient } from 'agentfootprint/providers';
257
257
 
258
258
  const slack = await mcpClient({
259
259
  name: 'slack',
@@ -47,8 +47,8 @@ Build with `mock()` provider + `InMemoryStore` + `mockEmbedder()` + inline tool
47
47
 
48
48
  | Mock | Production swap |
49
49
  |---|---|
50
- | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `anthropic()` / `openai()` / `bedrock()` / `ollama()` |
51
- | `InMemoryStore` | `RedisStore` (`agentfootprint/memory-redis`) · `AgentCoreStore` (`agentfootprint/memory-agentcore`) · Dynamo · Postgres · Pinecone (planned) |
50
+ | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `ollama('<model>')` a real model, still $0 and no key · then `anthropic()` / `openai()` / `bedrock()` |
51
+ | `InMemoryStore` | `RedisStore` (`agentfootprint/memory`) · `AgentCoreStore` (`agentfootprint/memory`) · Dynamo · Postgres · Pinecone (planned) |
52
52
  | `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
53
53
  | `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` real server |
54
54
 
@@ -47,8 +47,8 @@ Build with `mock()` provider + `InMemoryStore` + `mockEmbedder()` + inline tool
47
47
 
48
48
  | Mock | Production swap |
49
49
  |---|---|
50
- | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `anthropic()` / `openai()` / `bedrock()` / `ollama()` |
51
- | `InMemoryStore` | `RedisStore` (`agentfootprint/memory-redis`) · `AgentCoreStore` (`agentfootprint/memory-agentcore`) · Dynamo · Postgres · Pinecone (planned) |
50
+ | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `ollama('<model>')` a real model, still $0 and no key · then `anthropic()` / `openai()` / `bedrock()` |
51
+ | `InMemoryStore` | `RedisStore` (`agentfootprint/memory`) · `AgentCoreStore` (`agentfootprint/memory`) · Dynamo · Postgres · Pinecone (planned) |
52
52
  | `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
53
53
  | `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` real server |
54
54
 
@@ -47,8 +47,8 @@ Build with `mock()` provider + `InMemoryStore` + `mockEmbedder()` + inline tool
47
47
 
48
48
  | Mock | Production swap |
49
49
  |---|---|
50
- | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `anthropic()` / `openai()` / `bedrock()` / `ollama()` |
51
- | `InMemoryStore` | `RedisStore` (`agentfootprint/memory-redis`) · `AgentCoreStore` (`agentfootprint/memory-agentcore`) · Dynamo · Postgres · Pinecone (planned) |
50
+ | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `ollama('<model>')` a real model, still $0 and no key · then `anthropic()` / `openai()` / `bedrock()` |
51
+ | `InMemoryStore` | `RedisStore` (`agentfootprint/memory`) · `AgentCoreStore` (`agentfootprint/memory`) · Dynamo · Postgres · Pinecone (planned) |
52
52
  | `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
53
53
  | `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` real server |
54
54
 
@@ -47,8 +47,8 @@ Build with `mock()` provider + `InMemoryStore` + `mockEmbedder()` + inline tool
47
47
 
48
48
  | Mock | Production swap |
49
49
  |---|---|
50
- | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `anthropic()` / `openai()` / `bedrock()` / `ollama()` |
51
- | `InMemoryStore` | `RedisStore` (`agentfootprint/memory-redis`) · `AgentCoreStore` (`agentfootprint/memory-agentcore`) · Dynamo · Postgres · Pinecone (planned) |
50
+ | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `ollama('<model>')` a real model, still $0 and no key · then `anthropic()` / `openai()` / `bedrock()` |
51
+ | `InMemoryStore` | `RedisStore` (`agentfootprint/memory`) · `AgentCoreStore` (`agentfootprint/memory`) · Dynamo · Postgres · Pinecone (planned) |
52
52
  | `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
53
53
  | `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` real server |
54
54
 
@@ -47,8 +47,8 @@ Build with `mock()` provider + `InMemoryStore` + `mockEmbedder()` + inline tool
47
47
 
48
48
  | Mock | Production swap |
49
49
  |---|---|
50
- | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `anthropic()` / `openai()` / `bedrock()` / `ollama()` |
51
- | `InMemoryStore` | `RedisStore` (`agentfootprint/memory-redis`) · `AgentCoreStore` (`agentfootprint/memory-agentcore`) · Dynamo · Postgres · Pinecone (planned) |
50
+ | `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `ollama('<model>')` a real model, still $0 and no key · then `anthropic()` / `openai()` / `bedrock()` |
51
+ | `InMemoryStore` | `RedisStore` (`agentfootprint/memory`) · `AgentCoreStore` (`agentfootprint/memory`) · Dynamo · Postgres · Pinecone (planned) |
52
52
  | `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
53
53
  | `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` real server |
54
54