{"id":48515300,"url":"https://github.com/footprintjs/agentfootprint","last_synced_at":"2026-06-17T01:01:53.336Z","repository":{"id":344916790,"uuid":"1183666171","full_name":"footprintjs/agentfootprint","owner":"footprintjs","description":"Context engineering, abstracted. Build AI agents whose every LLM call traces back to what was injected, who triggered it, when, and how it cached. Built on footprintjs","archived":false,"fork":false,"pushed_at":"2026-06-11T01:54:20.000Z","size":6450,"stargazers_count":9,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-06-11T02:22:32.852Z","etag":null,"topics":["agent-framework","agentic-ai","ai-agents","ai-safety","explainability","human-in-the-loop","llm","observability","rag","tool-use"],"latest_commit_sha":null,"homepage":"https://footprintjs.github.io/agentfootprint/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/footprintjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-16T20:49:43.000Z","updated_at":"2026-06-11T01:54:23.000Z","dependencies_parsed_at":"2026-04-22T07:01:58.246Z","dependency_job_id":null,"html_url":"https://github.com/footprintjs/agentfootprint","commit_stats":null,"previous_names":["footprintjs/agentfootprint"],"tags_count":117,"template":false,"template_full_name":null,"purl":"pkg:github/footprintjs/agentfootprint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/footprintjs%2Fagentfootprint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/footprintjs%2Fagentfootprint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/footprintjs%2Fagentfootprint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/footprintjs%2Fagentfootprint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/footprintjs","download_url":"https://codeload.github.com/footprintjs/agentfootprint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/footprintjs%2Fagentfootprint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34429493,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-framework","agentic-ai","ai-agents","ai-safety","explainability","human-in-the-loop","llm","observability","rag","tool-use"],"created_at":"2026-04-07T19:00:38.567Z","updated_at":"2026-06-17T01:01:53.319Z","avatar_url":"https://github.com/footprintjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n\u003ch1 align=\"center\"\u003eAgentfootprint\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eYour agent gave an answer that \u003cem\u003elooks\u003c/em\u003e right — and it's wrong.\u003cbr/\u003eThe logs can't tell you who influenced it. Agentfootprint can.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  The explainable agent framework: every read, write, decision, and tool call becomes\n  \u003cstrong\u003econnected evidence\u003c/strong\u003e as your agent runs. When something goes wrong, you don't grep logs — you ask.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://footprintjs.github.io/agentThinkingUI/\"\u003e\n    \u003cimg src=\"docs/assets/hero-atui.png\" alt=\"An agent run replayed in AgentThinkingUI — the LLM 'brain' calls the Flight-search tool, the step inspector shows the tool's raw output and the brain's reasoning about it, and the timeline scrubs every step of the run.\" width=\"100%\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003csub\u003eA real run, replayed — rendered with \u003ca href=\"https://github.com/footprintjs/agentThinkingUI\"\u003e\u003cb\u003eAgentThinkingUI\u003c/b\u003e\u003c/a\u003e (\u003ccode\u003enpm i agentthinkingui\u003c/code\u003e). Every frame is generated from the run's own trace; \u003ca href=\"https://footprintjs.github.io/agentThinkingUI/\"\u003e▶ watch it live\u003c/a\u003e.\u003c/sub\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/footprintjs/agentfootprint/actions\"\u003e\u003cimg src=\"https://github.com/footprintjs/agentfootprint/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003c!-- coverage-badge --\u003e\u003cimg src=\"https://img.shields.io/badge/coverage-87%25-green.svg\" alt=\"coverage: 87%\"\u003e\u003c!-- /coverage-badge --\u003e\n  \u003ca href=\"https://www.npmjs.com/package/agentfootprint\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/agentfootprint.svg?style=flat\" alt=\"npm version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/package/agentfootprint\"\u003e\u003cimg src=\"https://img.shields.io/bundlephobia/minzip/agentfootprint?label=minzipped\" alt=\"minzipped size\"\u003e\u003c/a\u003e\n  \u003ca href=\"#tree-shakeable--esm-first\"\u003e\u003cimg src=\"https://img.shields.io/badge/tree--shakeable-%E2%9C%93-success?style=flat\" alt=\"tree-shakeable\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/agentfootprint\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/agentfootprint.svg\" alt=\"Downloads\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/footprintjs/agentfootprint/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"MIT\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## The new error class\n\nFor decades, software had two kinds of errors — and developers never needed deep\ndomain knowledge to fix either:\n\n| Error class | Where the bug lives | How you find it |\n|---|---|---|\n| **Infrastructure** — crash, timeout, 500 | the system | infra logs, monitoring |\n| **Business logic** — wrong branch, wrong math | the code | stack trace, debugger, `console.log` |\n| **Contextual** — wrong tool chosen, wrong fact believed, stale memory trusted | **what the model was given** | **nothing. Until now.** |\n\nAgents introduced the third class. The code is correct, the infra is healthy, the\nanswer even reads well — and the run is still wrong, because something influenced\nthe model:\n\n| The model… | because… |\n|---|---|\n| picked the wrong tool | two descriptions read nearly alike — it chose between twins |\n| believed a wrong \"fact\" | a tool returned it, or an injected fact planted it |\n| followed the wrong instruction | the wrong skill / steering fired — or fired one iteration too early |\n| answered from the past | a previous turn or stale memory bled into this one |\n\nClassical logs can't explain any of it: **they record what the code did, never\nwhat the context did.** The debugging question changed — no longer *\"what did my\ncode do?\"* but **\"who influenced the model?\"**\n\n## The idea\n\nIf contextual errors live in what the model was given, then the run itself must be\nstructured so context is **evidence** — every injection, read, write, decision, and\ntool call recorded *connected*, the moment it happens. Not logs you grep. Evidence\nyou ask.\n\n## How — we abstract context engineering\n\nEvery piece of context enters the LLM through one of **3 slots** (`system` ·\n`messages` · `tools`), under one of **4 triggers** — skills, steering, RAG, facts,\nmemory, guardrails are all the same move: `Injection = slot × trigger × cache`.\n\n**Because the framework owns that injection point, every piece of context is born\ntracked.** Tracking isn't an add-on you wire up — it's a consequence of the\nabstraction. [The full model ↓](#the-model--what-we-abstract)\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/hero-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/hero-light.svg\"\u003e\n    \u003cimg 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%\"/\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n## What tracking buys you\n\n**See it in 30 seconds** — four questions logs can't answer, each answered by code in this repo from a real run:\n\n```text\nQ: Why did the model pick refund_full instead of refund_partial?\nA: margin 0.02 — ⚠ NARROW: the two tool descriptions read nearly identical\n   (toolChoiceRecorder — and the catalog lint flags the pair before you ever run)\n\nQ: Why was this loan declined?\nA: decision ← [control: \"DTI above the 0.43 affordability ceiling\"] ← dti 0.52 ← monthlyDebt / income\n   (decide() evidence + the causal slice — every hop is a real recorded edge)\n\nQ: Which piece of context made the answer wrong?\nA: CAUSAL: ablating fact 'vip-override' flipped the outcome in 3/3 seeded reruns\n   (localizeContextBug — ranked proxies, counterfactual proof)\n\nQ: Prove nobody edited this run's record.\nA: verifyAuditBundle → valid: false, brokenAt: #16 — the tampered record, named\n   (hash-chained audit export, offline verification)\n```\n\nAnd you don't have to read the trace yourself — **we provide the tools for an LLM to track it for you**: the trace toolpack let a debugger model find a planted bug while reading **9.5% of the trace** ([guide](docs/guides/trace-debugging.md)).\n\n**And all this watching costs the run nothing.** Your agent *is* the event loop: a stage runs on the call stack and feeds its trace events into a queue; in the idle beat the dispatcher delivers them to your listeners and files them in trace memory — **one beat behind**, never blocking the hot path:\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/event-loop-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/event-loop-light.svg\"\u003e\n    \u003cimg alt=\"Your agent is the event loop — animated. Left: your agent code (Context, Call LLM, Tool Calls) looping turn after turn. Right: the JS event loop drawn as two bold curved arrows with a traveling cursor and two stops — the call stack, where each stage runs as a frame and feeds four trace events (structure, data, control, emit) into the trace queue at the loop's center; and idle time, where the dispatcher flies the queued events into TRACE MEMORY and every listener (onStageAdded, onCommit, onDecision, onEmit) receives every event, one beat behind. Grey is JavaScript's own machinery, green is footprintjs, colors are your code and its trace.\" src=\"docs/assets/event-loop-light.svg\" width=\"100%\"/\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n## One contextual error, walked end to end\n\nThe third question above, in full — every value below is the captured output of\n[`examples/observability/05-context-bisect.ts`](examples/observability/05-context-bisect.ts)\nand [`06-backtrack-trace.ts`](examples/observability/06-backtrack-trace.ts), runnable offline.\n\n**The bug.** A refunds agent carries a poisoned customer-profile fact. It answers:\n\n\u003e *\"Refund APPROVED: Dana Reyes holds VIP tier override status, so the 47-day-old\n\u003e order qualifies for a refund beyond the 30-day window.\"*\n\nThe policy says 30 days. The logs look fine — the model was *given* bad context,\nand classical logging has no row for that.\n\n**The walk.** Because context here is state, the decision backtracks like a\nvariable: who read it, who wrote it, who let it in, where it was born —\n\n```text\nANSWER   \"Refund APPROVED…\"                                   ← the bug\nREAD     call-llm#40 assembled the system prompt              ← exactly what the model saw\nLANDED   context#6 wrote systemPromptInjections               ← who mutated state\nALLOWED  trigger { kind: 'always' } — active every iteration  ← why it was let in\nBORN     defineFact('vip-override-fact')                      ← who wrote it\n```\n\nThat chain gives the **complete, provable candidate set** — every piece of\ncontext that demonstrably reached the call, and nothing else. Influence scoring\nthen *ranks* inside it (the two facts sit 0.01 apart — proxies can't separate\nthem), and counterfactual **ablation proves**: removing `vip-override-fact`\nflips APPROVED → DECLINED in **3/3 seeded reruns**; the benign style fact and\nthe lookup tool come back not-confirmed, 0/3. Scores are proxies; only the\nablation verdict makes a causal claim — the report says so itself.\n\n**And when the proxy *can't* rank — it says so.** Output-similarity scoring is\nstructurally blind to **absence/crowding** bugs (a key instruction truncated out\nof the window, context diluted by filler): the culprit doesn't resemble the\nanswer, so it ranks low under an innocent. `rankingConfidence` is the honesty\nmarker for that — when no source clearly wins, it returns a **shortlist to\nconfirm by ablation** instead of a confident, wrong #1:\n\n```typescript\nimport { rankingConfidence, ratioStrategy } from 'agentfootprint/observe';\n\nconst c = rankingConfidence(scores); // over a scoreInfluence() result\nif (!c.clearWinner) ablate(c.shortlist); // too flat to trust → escalate to truth\n// decisiveness rule is pluggable: marginStrategy (default) · ratioStrategy\n// (scale-invariant) · bring-your-own. See docs/guides/ranking-confidence.md\n```\n\n**Three interfaces, one for each shape of the bug** — ship-a-default, bring-your-own:\n\n| interface | finds the culprit when it is… | confirm by |\n|---|---|---|\n| **influence ranking** (`scoreInfluence` + `rankingConfidence`) | **present** — orders suspects, says when it can't | — |\n| **ablation** (`localizeContextBug`) | **present** — *remove* it, see the outcome flip | removal |\n| **missing-context finder** (`findDroppedContext`) | **absent** — available but never reached the model (`available − sent`) | restoration |\n\nThe third closes the gap the first two are blind to — a key instruction truncated\nout of the window has nothing to ablate. `findDroppedContext` is a cheap, exact id\ndiff (no embeddings, no LLM); confirm by *restoration* — add the dropped unit back,\nsee if the outcome flips. [Guide](docs/guides/missing-context.md) ·\n[example](examples/observability/10-missing-context.ts).\n\n**The same walk, visual.** One call serializes the report for\n[AgentThinkingUI](https://github.com/footprintjs/agentThinkingUI)'s\n`\u003cBacktrackView\u003e` — the \"why?\" board, triggerable from **any** decision point\n(final answer, a mid-loop tool choice, a deterministic `decide()` rule):\n\n```typescript\nimport { localizeContextBug, toBacktrackTrace } from 'agentfootprint/observe';\n\nconst report = await localizeContextBug({ artifacts, embedder, atStep, rerun });\nconst trace = toBacktrackTrace(report, {\n  claim: 'The agent approved a refund 47 days past the 30-day window — why?',\n  answer: { text: buggyAnswer, label: 'the wrong answer' },\n});\n// \u003cBacktrackView trace={trace}/\u003e — or \u003cBacktrackOverlay/\u003e from any decision point\n```\n\n\u003cimg alt=\"The BacktrackView board: the wrong answer, the suspects with influence meters, the CAUSAL 3/3 ablation stamp on the planted fact, and the chain-of-custody rewind showing the exact system prompt the model saw with the culprit sentence highlighted.\" src=\"docs/assets/backtrack-board.png\" width=\"100%\"/\u003e\n\u003cp align=\"center\"\u003e\n  \u003csub\u003e\u003ca href=\"https://footprintjs.github.io/agentThinkingUI/demo/backtrack.html\"\u003e\u003cb\u003e▶ Try the why-board live\u003c/b\u003e\u003c/a\u003e — or run \u003ca href=\"examples/observability/06-backtrack-trace.ts\"\u003e\u003ccode\u003eexamples/observability/06-backtrack-trace.ts\u003c/code\u003e\u003c/a\u003e offline.\u003c/sub\u003e\n\u003c/p\u003e\n\nThe rewind pane at the bottom is the killer view: **the exact system prompt the\nmodel saw**, with the culprit sentence highlighted — recorded state, not a\nreconstruction. And the same chain feeds the machine door: every id on the\nboard is a `runtimeStageId` a debugger LLM can drill with the\n[trace toolpack](docs/guides/trace-debugging.md), token-cheaply.\n\n---\n\n## Pick your door\n\n| 🔧 Building an agent? | 🐛 Agent misbehaving? | 🏛️ Need audit / compliance? |\n|---|---|---|\n| Typed agents with skills, steering, RAG, memory, guardrails — and the trace for free. | Lint your tool catalog in 5 minutes — works on **any** framework's tool list (plain JSON / MCP / OpenAI / Anthropic shapes). Then causal slices, context bisection, and the debugger-LLM toolpack. | Hash-chained, tamper-evident run records with an offline verifier — record-keeping in the EU-AI-Act shape. |\n| [→ Quick start](#quick-start--runs-offline-no-api-key) · [→ Build ↓](#-build--design-your-agent-or-system-of-agents) | [→ Debug ↓](#-debug--see-what-your-agent-did) · [→ Tool-catalog lint](docs/guides/tool-catalog-lint.md) · [→ Trace debugging](docs/guides/trace-debugging.md) | [→ Audit ↓](#-audit--prove-what-happened) · [→ Security guide](docs/guides/security.md) |\n\n---\n\n## Quick start — runs offline, no API key\n\n```bash\nnpm install agentfootprint footprintjs\n```\n\n```typescript\nimport { Agent, defineTool, mock } from 'agentfootprint';\n\nconst weather = defineTool({\n  name: 'weather',\n  description: 'Get current weather for a city.',\n  inputSchema: {\n    type: 'object',\n    properties: { city: { type: 'string' } },\n    required: ['city'],\n  },\n  execute: async ({ city }: { city: string }) =\u003e `${city}: 72°F, sunny`,\n});\n\nconst agent = Agent.create({\n  provider: mock({ reply: 'I checked: it is 72°F and sunny.' }),\n  model: 'mock',\n})\n  .system('You answer weather questions using the weather tool.')\n  .tool(weather)\n  .build();\n\nconst result = await agent.run({ message: 'Weather in Paris?' });\nconsole.log(result);  // → \"I checked: it is 72°F and sunny.\"\n```\n\nFor 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.)\n\n### Then add context\n\nA real agent carries more than one prompt and one tool: facts about the user, always-on rules, skills that unlock on demand. Declare each piece — the framework decides **when** it fires and **which slot** it lands in, and every piece is born tracked:\n\n```typescript\nimport { defineFact, defineSteering, defineSkill } from 'agentfootprint';\n\nconst agent = Agent.create({ provider, model })\n  .system('You are a support agent.')\n  .fact(defineFact({                    // data the model should know — always on\n    id: 'user-profile',\n    data: 'Name: Maya · Plan: Pro · Customer since 2022',\n  }))\n  .steering(defineSteering({            // rules the model must follow — always on\n    id: 'refund-policy',\n    prompt: 'Never promise a refund before checking the policy tool.',\n  }))\n  .skill(defineSkill({                  // guidance + tools — unlocks when the LLM asks\n    id: 'billing',\n    description: 'Use for refunds, charges, billing questions.',\n    body: 'When handling billing: confirm identity first, then…',\n    tools: [refundTool],\n  }))\n  .build();\n```\n\nSame shape for `.instruction()` / `.memory()` / `.rag()` / raw `.injection()` — they're all the one primitive, `Injection = slot × trigger × cache`. [The full model ↓](#the-model--what-we-abstract)\n\n### Then compose control flow\n\nOne agent is a `Runner`. So is every composition of agents — four control-flow primitives, and anything that runs composes into anything else:\n\n```typescript\nimport { Sequence, Parallel, Conditional } from 'agentfootprint';\n\nconst pipeline = Sequence.create()\n  .step('classify', classifyAgent)                  // sequence: step → step\n  .step('review',\n    Parallel.create()                               // parallel: fan out, then merge\n      .branch('legal', legalAgent)\n      .branch('ethics', ethicsAgent)\n      .mergeWithLLM({ provider, model, prompt: 'Synthesize:' })\n      .build())\n  .step('respond',\n    Conditional.create()                            // conditional: one branch runs\n      .when('urgent', (i) =\u003e i.message.startsWith('URGENT'), urgentAgent)\n      .otherwise('normal', normalAgent)\n      .build())\n  .build();\n\nawait pipeline.run({ message: 'URGENT: refund dispute on order #4411' });\n```\n\nThe fourth primitive is `Loop` — `Loop.repeat(agent).until(guard).times(5)`, with a mandatory budget guard. And the named patterns from the research literature ship pre-composed from the same four: `selfConsistency` · `reflection` · `debate` · `mapReduce` · `tot` · `swarm`. Because every composition is a flowchart, the structure you wrote is the structure you see in the UI — and the trace spans the whole pipeline, not one agent at a time. [Designing systems of agents ↓](#-build--design-your-agent-or-system-of-agents)\n\n---\n\n## The model — what we abstract\n\n\n\nWhen you build an Agentic Application, you collect domain-specific data and instructions, then wire them up based on what your system receives.\n\nThat 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`).\n\nSo we abstracted the injection itself.\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/triggers-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/triggers-light.svg\"\u003e\n    \u003cimg 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%\"/\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\nThe abstraction is three rules:\n\n1. **Three slots are fixed.** `system`, `messages`, `tools` — the LLM API surface.\n2. **N flavors are open.** You declare what you have. Tomorrow's flavor (few-shot, reflection, persona, A2A handoff…) plugs in the same way.\n3. **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.\n\nThat's the whole model: `Injection = slot × trigger × cache`.\n\n- **Slot** — which of the 3 LLM API regions the content lands in (`system` / `messages` / `tools`).\n- **Trigger** — when the content fires (see below).\n- **Cache** — how stable the content is across iterations. The framework places provider cache markers for you — stable content gets 80–90% cheaper prefixes.\n\n### The 4 triggers\n\n| Trigger | Flavor | Fires when | Illustration | Default slot |\n|---|---|---|---|---|\n| `always` | static | Every iteration | `.steering(defineSteering({ id, prompt: 'You are a triage agent…' }))` | `system` |\n| `rule` | runtime — predicate | Your rule returns true | `.instruction(defineInstruction({ id, activeWhen: s =\u003e /price\\|refund/.test(s.userQuery), prompt }))` | `system` |\n| `on-tool-return` | runtime — lifecycle | After a specific tool returns | `.instruction(defineInstruction({ id, slot: 'messages', activeWhen, prompt: 'Cite source IDs.' }))` | `messages` |\n| `llm-activated` | runtime — agent-driven | LLM calls `read_skill('id')` | `.skill(defineSkill({ id: 'refund-policy', description, body, viaToolName: 'read_skill' }))` | `messages` (body) |\n\n\u003e [!NOTE]\n\u003e The \"Illustration\" column shows the shape of each flavor — the typed builder methods (`.steering` / `.instruction` / `.skill` / `.fact` / `.rag`) take an `Injection` (or `MemoryDefinition` for `.rag`) produced by the matching `defineSteering` / `defineInstruction` / `defineSkill` / `defineFact` / `defineRAG` factory. Slot is a default, not a coupling — the same `Skill` can live in `tools` (schema only, discovered via `read_skill`), `messages` (body injected on activation), or `system` (baked into the prompt as steering).\n\n**3 slots × 4 triggers × N flavors = the entire context-engineering surface.**\n\n---\n\n## Why we chose this abstraction\n\nThe agent space has many credible primary abstractions:\n\n| Framework | What it abstracts |\n|---|---|\n| **LangChain** | Pipelines of composable components |\n| **LangGraph** | State machines of nodes and edges |\n| **CrewAI · AutoGen** | Crews of role-playing agents |\n| **Mastra · Genkit · Pydantic AI** | Typed full-stack bundles |\n| **DSPy** | Compiled prompts |\n| **Inngest AgentKit** | Durable workflows |\n\nWe didn't have to choose between them.\n\nagentfootprint is built on **footprintjs** — the flowchart pattern for backend code. footprintjs gives us every one of those abstractions out of the box:\n\n| Capability | What footprintjs hands us |\n|---|---|\n| Composition | `Sequence` · `Parallel` · `Conditional` · `Loop` |\n| State machines | The ReAct loop *is* a flowchart |\n| Multi-agent crews | Compose Agents through control flow — no special class needed |\n| Durable workflows | `pauseHere()` plus JSON-portable `resume()` |\n| Typed observation | 60+ events for free, because the framework owns the loop |\n\nSo we used the budget those abstractions would have cost us to invest deeply in something they all leave to the developer: **the injection loop.**\n\n\u003e [!IMPORTANT]\n\u003e **We abstract context engineering — and hand back the trace.**\n\u003e Live to develop · offline to monitor · detailed to improve.\n\n---\n\n## 🔧 Build — design your agent or system of agents\n\nTwo scales — same alphabet. Four control flows are the entire vocabulary.\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\" align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/sequence-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/sequence-light.svg\"\u003e\n    \u003cimg alt=\"Sequence — linear chain A → B → C.\" src=\"docs/assets/sequence-light.svg\" width=\"100%\"/\u003e\n  \u003c/picture\u003e\n\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\n\n```typescript\nimport { Sequence } from 'agentfootprint';\n\nconst flow = Sequence.create()\n  .step('a', stageA)\n  .step('b', stageB)\n  .step('c', stageC)\n  .build();\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\" align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/parallel-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/parallel-light.svg\"\u003e\n    \u003cimg alt=\"Parallel — fan-out then fan-in across N agents.\" src=\"docs/assets/parallel-light.svg\" width=\"100%\"/\u003e\n  \u003c/picture\u003e\n\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\n\n```typescript\nimport { Parallel } from 'agentfootprint';\n\nconst fan = Parallel.create()\n  .branch('web', searchWeb)\n  .branch('docs', searchDocs)\n  .mergeWithFn(synthesizer)\n  .build();\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\" align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/conditional-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/conditional-light.svg\"\u003e\n    \u003cimg alt=\"Conditional — diamond gate routes to one of N branches based on a predicate.\" src=\"docs/assets/conditional-light.svg\" width=\"100%\"/\u003e\n  \u003c/picture\u003e\n\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\n\n```typescript\nimport { Conditional } from 'agentfootprint';\n\nconst router = Conditional.create()\n  .when('billing', s =\u003e /bill|invoice|refund/.test(s.message), billingAgent)\n  .when('tech',    s =\u003e /error|bug|crash/.test(s.message),     techAgent)\n  .otherwise('default', defaultAgent)\n  .build();\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\" align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/loop-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/loop-light.svg\"\u003e\n    \u003cimg alt=\"Loop — body cycles back from end to start until a condition is met.\" src=\"docs/assets/loop-light.svg\" width=\"100%\"/\u003e\n  \u003c/picture\u003e\n\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\n\n```typescript\nimport { Loop } from 'agentfootprint';\n\nconst reflexion = Loop.create()\n  .repeat(thinkAgent)\n  .until(({ latestOutput }) =\u003e latestOutput.includes('DONE'))\n  .build();\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n### Inside one agent — Dynamic vs Classic ReAct\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/dynamic-vs-classic-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/dynamic-vs-classic-light.svg\"\u003e\n    \u003cimg 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%\"/\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n**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.\n\n| Iteration | Classic ReAct | Dynamic ReAct (agentfootprint) |\n|---|---|---|\n| 1 | 12 tools shown | **1 tool** (`read_skill`) |\n| 2 | 12 tools shown | **5 tools** (skill activated) |\n| 3 | 12 tools shown | 5 tools |\n\n\u003e 📖 [Dynamic ReAct guide](https://footprintjs.github.io/agentfootprint/guides/dynamic-react/) · [Key concepts](https://footprintjs.github.io/agentfootprint/getting-started/key-concepts/)\n\n### Multi-agent — compose with the alphabet\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/compose-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/compose-light.svg\"\u003e\n    \u003cimg 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%\"/\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\nPick the flows that match your problem. Chain them. **That's your Agentic Application.**\n\n```typescript\nconst research = Loop.create()\n  .repeat(Sequence.create().step('plan', plan).step('search', searchAll).build())\n  .until(({ iteration, latestOutput }) =\u003e iteration \u003e= 3 || latestOutput.includes('DONE'))\n  .build();\n```\n\nSame `.create().method().build()` shape as the four rows above — just composed.\n\n### Named patterns — also compositions of the same 4\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/patterns-dark.svg\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/patterns-light.svg\"\u003e\n    \u003cimg 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%\"/\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\nThe patterns the field knows reduce to the same alphabet:\n\n| Pattern | Composition |\n|---|---|\n| **Swarm** | `Loop( Parallel( Agent×N ) → merge )` |\n| **Tree-of-Thoughts** | `Loop( Parallel( Agent×N ) → Conditional(score) )` |\n| **Reflexion** | `Loop( Agent → Conditional(critique) → Agent )` |\n| **Debate** | `Parallel( Agent_pro, Agent_con ) → Agent_judge` |\n| **Router** | `Conditional → Agent_A \\| Agent_B \\| Agent_C` |\n| **Hierarchical** | `Agent_planner → Sequence( Agent_worker×N ) → synth` |\n\nSame trick as the injection model: instead of N libraries for N patterns, we found the M building blocks all N patterns are made of.\n\n\u003e 📖 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/)\n\n---\n\n## 🐛 Debug — see what your agent did\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/lens-run.png\" alt=\"A real agent run in the Lens: the conversation (with live PII redaction), the executed path lit on the merge-tree flowchart, the WHAT-HAPPENED timeline of every iteration/context/LLM turn/route, run stats, and the step inspector — all generated from the run's own trace.\" width=\"100%\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003csub\u003eOne real run, fully explained — the \u003ca href=\"https://github.com/footprintjs/agentfootprint-lens\"\u003e\u003cb\u003eLens\u003c/b\u003e\u003c/a\u003e (\u003ccode\u003enpm i agentfootprint-lens\u003c/code\u003e): conversation · executed path · per-step timeline · stats, every pixel from the trace.\u003c/sub\u003e\n\u003c/p\u003e\n\nBecause we own the loop, 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 — and every LLM call backtracks to four typed answers: **what** was injected, **who** triggered it (which rule), **when** it fired, **how** it landed (slot · position · cache). Beyond the default, wire custom recorders for cost, latency, or quality scoring — any observation hook fires on the same stream.\n\nThe same trace serves three downstream consumers — no extra instrumentation:\n\n1. **Audit / compliance.** Six months later, *\"why was loan #42 rejected?\"* answers from the chain (`creditScore=580 \u003c 620 ∧ dti=0.6 \u003e 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.\n\n2. **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.\n\n3. **Training data — the substrate is already there.** Every successful chain is a labeled trajectory. SFT pairs (`{prompt, completion}`) fall out of the snapshot's history field; the export wrapper is roadmap work tracked in [GitHub issues](https://github.com/footprintjs/agentfootprint/issues). DPO and process-RL need additional collection layers (preference feedback, per-step reward annotation) that don't ship today.\n\nFour views, one trace — pick by question:\n\n| View | Shows | When to use |\n|---|---|---|\n| **AgentThinkingUI** (the hero up top) | The run replayed as an animated, scrubbable story — the brain, the tools, the reasoning | Show anyone *what the agent did* |\n| **BacktrackView** ([the board above](#one-contextual-error-walked-end-to-end)) | A decision walked backwards — suspects, influence meters, ablation stamps, custody rewind | Answer *why it decided that* |\n| **Lens** | Agent-centric — User/Agent[3 slots]/Tool flowchart with iteration scrubber and round commentary | Live debugging, \"what did the agent see at step 5?\" |\n| **Explainable Trace** | Structural — subflow tree, full flowchart, memory inspector, per-stage execution timeline | Architecture review, root-cause analysis |\n\nAnd two **conversational** doors over the same evidence — ask instead of look:\n\n```ts\n// dedicated: a cheap model debugs an expensive run by id — pays for what it opens\nconst debuggerAi = traceDebugAgent({ artifacts, provider: anthropic(), model: 'claude-haiku-4-5' });\nawait debuggerAi.run({ message: 'Why was loan APP-7 approved?' });\n\n// in-conversation: the agent answers \"why did you…?\" from its OWN previous turn\nAgent.create({ provider, model }).tool(lookupOrder)\n  .selfExplain({ delegate: { provider: anthropic(), model: 'claude-haiku-4-5' } })\n  .build();\n```\n\n`.selfExplain()` mounts one skill: the catalog stays clean until the LLM activates\nit, evidence binds only to **completed** runs (never in-flight), and `delegate`\nanswers at the cheap model's price inside the expensive conversation.\n[Guide](docs/guides/trace-debugging.md) · examples\n[`07`](examples/observability/07-trace-debug-agent.ts) ·\n[`08`](examples/observability/08-self-explain.ts) · the doors walk the\n[**same evidence the board visualizes ▶**](https://footprintjs.github.io/agentThinkingUI/demo/backtrack.html).\n\n\u003e 📖 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 \u0026 compliance](https://footprintjs.github.io/footPrint/blog/explainability-compliance/)\n\n**One recording. Two lenses. Three consumers. Zero extra instrumentation.**\n\n### Observers stay off the hot path\n\nBy default every `agent.on()` listener runs synchronously inside the producing\nstatement. One option moves observation off the hot path:\n\n```ts\nAgent.create({ provider, model, observerDelivery: 'deferred' }) // default 'inline'\n// serverless / shutdown: settle async listener work before the freeze\nawait agent.drainObservers({ timeoutMs: 5_000 });\n```\n\nEvents are captured into a bounded queue (≈ microseconds on the hot path) and\ndelivered one beat behind — same typed events, same order, zero loss, a throwing\nlistener can't kill the run, and per-listener stats land on\n`getLastSnapshot()?.observerStats` to name the hog. Terminal boundaries (resolve,\ncrash, pause) drain synchronously first, so checkpoints are always complete.\nMeasured: −8% wall on a 50-iteration agent with a deliberately slow listener\n([example 21](examples/features/21-deferred-observers.ts)).\n\n\u003e 📖 Full semantics (capture policies, backpressure, overflow):\n\u003e [deferred-observers guide](https://github.com/footprintjs/footPrint/blob/main/docs/guides/observers-deferred.md)\n\n### Lint your tool catalog — before the model picks the wrong twin\n\nTool routing is an LLM decision driven by names + descriptions — so lint the\ncatalog like code and gate it in CI. **Zero stack buy-in**: works on any\nOpenAI / Anthropic / MCP / plain tool list, no agentfootprint runtime needed.\n\n```bash\nnpx agentfootprint-lint-tools tools.json --threshold 0.94 --strict\n```\n\n```\n✗ CONFUSABLE 0.9445  get_fcns_database \u003c\u003e influx_get_fcns_database\n    hint: names differ only by 'influx' — make the descriptions say WHEN to choose each\n~ warn  [enum-in-prose] influx_get_port_ranking.metric\n    suggest: \"enum\": [\"avg_iops\",\"peak_iops\",\"mbps\"]\n```\n\nPairwise confusability over what the model reads (embedder pluggable,\ncontent-hash cached) plus a pluggable structural rule pack\n(missing/short descriptions, says-WHAT-not-WHEN, enums hiding in prose,\nundocumented optional params). The runtime counterpart, `toolChoiceRecorder`\n(`agentfootprint/observe`), scores each live LLM call's tool choice against\nthe same geometry and flags narrow margins and proxy disagreements — lazily,\noff the hot path.\n\n\u003e 📖 **[Tool-catalog lint guide](docs/guides/tool-catalog-lint.md)** — 5 minutes\n\u003e from a tools.json to a gated CI check ·\n\u003e [`examples/observability/02`](examples/observability/02-lint-confusable-catalog.ts) ·\n\u003e [`03`](examples/observability/03-lint-fix-and-pass.ts) ·\n\u003e [`04`](examples/observability/04-tool-choice-margins.ts)\n\n---\n\n## 🏛️ Audit — prove what happened\n\nAnswering *\"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.\n\n```ts\nimport { auditExport, verifyAuditBundle } from 'agentfootprint/observability-providers';\n\nconst audit = auditExport({ agent: 'ledger-auditor' });\nconst stop = agent.enable.observability({ strategy: audit });\nawait agent.run({ message: 'audit account ACCT-1142' });\nstop();\n\nconst bundle = audit.bundle();           // plain JSON — store anywhere\nverifyAuditBundle(bundle);               // { valid: true, recordsChecked: 50 }\n// flip one byte anywhere → { valid: false, brokenAt: 13, reason: 'hash mismatch — …' }\n```\n\nPayloads are PII-bounded by default (tool args as key names, results as a type, content as `[N chars]` markers). And it's honest about its limits: tamper-**evident**, not tamper-proof — for non-repudiation, anchor both chain ends in external storage (WORM store, signed log).\n\n\u003e 📖 **[Tamper-evident audit guide](docs/guides/security.md#tamper-evident-audit-export--auditexport--verifyauditbundle)** ·\n\u003e [`examples/features/19-audit-export.ts`](examples/features/19-audit-export.ts) — capture → verify → tamper → drain ·\n\u003e [`20-regulated-decisioning.ts`](examples/features/20-regulated-decisioning.ts) — an offline auditor reconstructs a loan decline from persisted files, both chain ends anchored\n\n---\n\n## Mocks first, production second\n\nBuild the entire app against in-memory mocks with **zero API cost**, then swap real infrastructure one boundary at a time.\n\n| Boundary | Dev | Prod |\n|---|---|---|\n| LLM provider | `mock(...)` | `anthropic()` · `openai()` · `bedrock()` · `ollama()` |\n| Memory store | `InMemoryStore` | `RedisStore` · `AgentCoreStore` |\n| MCP | `mockMcpClient(...)` | `mcpClient({ transport })` |\n| Cache strategy | `NoOpCacheStrategy` | auto-selected per provider |\n\nThe flowchart, recorders, and tests don't change between dev and prod.\n\n---\n\n## What ships today\n\n**Core**\n- 2 primitives — `LLMCall`, `Agent` (the ReAct loop)\n- 4 control flows — `Sequence`, `Parallel`, `Conditional`, `Loop`\n- 1 Injection primitive — `defineSkill` / `defineSteering` / `defineInstruction` / `defineFact`\n- 1 reliability gate — `.reliability({ preCheck, postDecide, providers, circuitBreaker, fallback })`\n- 1 tool dispatch primitive — `ToolProvider` (sync OR async) — `staticTools` · `gatedTools` · `skillScopedTools` · or a custom `ToolProvider` that discovers over hubs / MCP / per-tenant catalogs\n\n**LLM providers** (7)\n\n| Factory | Use for |\n|---|---|\n| `anthropic` | Claude (Sonnet, Opus, Haiku) via `@anthropic-ai/sdk` |\n| `openai` | GPT-4o, GPT-4-turbo via `openai` SDK |\n| `bedrock` | Claude / Titan / Mistral via AWS Bedrock runtime |\n| `ollama` | Local models (OpenAI-compatible endpoint) |\n| `browserAnthropic` | Browser-side Claude calls (no proxy server) |\n| `browserOpenai` | Browser-side OpenAI calls (no proxy server) |\n| `mock` | Deterministic dev/test (zero API cost) |\n\n**Memory + adapters**\n- Memory factory — 4 types (`episodic` / `semantic` / `narrative` / `causal`) × 7 strategies (`window` / `budget` / `summarize` / `topK` / `extract` / `decay` / `hybrid`)\n- Memory stores — `InMemoryStore`, `RedisStore` (peer-dep `ioredis`), `AgentCoreStore` (peer-dep AWS SDK)\n- RAG · MCP adapters — `mockMcpClient(...)` / `mcpClient({ transport })`\n\n**Operability**\n- Provider-agnostic prompt caching — declarative per-injection, per-iteration marker recomputation\n- Pause / resume — JSON-serializable checkpoints; resume hours later on a different server\n- Resilience primitives — `withRetry`, `withFallback`, `withCircuitBreaker`, `.outputFallback`, `agent.resumeOnError`\n- 60+ typed observability events — `agent` · `composition` · `context` · `stream` · `tools` · `skill` · `memory` · `cache` · `cost` · `permission` · `eval` · `embedding` · `pause` · `error` · `fallback` · `resilience` · `reliability` · `risk`\n\n**Debugging \u0026 compliance** (`agentfootprint/observe`)\n- Tool-catalog lint — `npx agentfootprint-lint-tools` (any framework's tool list) + runtime `toolChoiceRecorder` margins\n- Contextual-bug localizer — `localizeContextBug` (causal slice → influence ranking → counterfactual ablation) + `bisectCulprits`\n- `toBacktrackTrace` — render any decision as the BacktrackView \"why?\" board\n- Trace toolpack — 6 bounded, LLM-callable tools so a debugger model walks the trace by id\n- `traceDebugAgent` (dedicated debugger session) · `.selfExplain()` (in-conversation why-questions, skill-gated, with a cheap-model `delegate` switch)\n- OTel GenAI span export · hash-chained tamper-evident audit bundles with an offline verifier\n\n**Tooling**\n- **AgentThinkingUI** — animated run player + BacktrackView why-board (separate `agentthinkingui` package)\n- **Lens** · **Explainable Trace** — two visual replays of the causal trace (separate `agentfootprint-lens` package)\n- AI-coding-tool support — Claude Code · Cursor · Windsurf · Cline · Kiro · Copilot\n\n\u003e 📖 [Agent API reference](https://footprintjs.github.io/agentfootprint/api/agent/) · [CHANGELOG](./CHANGELOG.md)\n\n---\n\n## Where to next\n\n| If you are... | Go here |\n|---|---|\n| New to agents | [5-minute quick start](https://footprintjs.github.io/agentfootprint/getting-started/quick-start/) |\n| Coming from LangChain / CrewAI / LangGraph | [Migration guide](https://footprintjs.github.io/agentfootprint/getting-started/vs/) |\n| Architecting an enterprise rollout | [Production guide](https://footprintjs.github.io/agentfootprint/guides/deployment/) |\n| Doing due diligence | [Architecture overview](https://footprintjs.github.io/agentfootprint/architecture/dependency-graph/) |\n| Researcher / academic background | [Citations \u0026 prior art](https://footprintjs.github.io/agentfootprint/research/citations/) |\n| Curious about design | [Inspiration docs](https://footprintjs.github.io/agentfootprint/inspiration/) |\n\nOr jump into the [examples gallery](https://github.com/footprintjs/agentfootprint/tree/main/examples) — every example is also an end-to-end CI test.\n\n---\n\n## Tree-shakeable \u0026 ESM-first\n\nImport one thing, ship one thing. agentfootprint is built so your bundle grows only with what you actually use:\n\n- **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.\n- **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.\n- **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.\n\n**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).\n\n---\n\n## Built on\n\n[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.\n\nYou 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/).\n\n---\n\n## License\n\n[MIT](./LICENSE) © [Sanjay Krishna Anbalagan](https://github.com/sanjay1909)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffootprintjs%2Fagentfootprint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffootprintjs%2Fagentfootprint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffootprintjs%2Fagentfootprint/lists"}