agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
229 lines (179 loc) • 8.37 kB
Markdown
# agentfootprint — Cursor Rules
Building Generative AI applications is mostly **context engineering** — deciding what content lands in which slot of the LLM call, when, and why. agentfootprint exposes this discipline through 2 primitives + 3 compositions + 1 unifying injection primitive + 1 memory factory.
## Core API (use these, not v1 names)
```typescript
import {
Agent, LLMCall, defineTool,
Sequence, Parallel, Conditional, Loop,
askHuman, pauseHere, isPaused,
} from 'agentfootprint';
import { defineSkill, defineSteering, defineInstruction, defineFact } from 'agentfootprint/context';
import {
defineMemory, MEMORY_TYPES, MEMORY_STRATEGIES, SNAPSHOT_PROJECTIONS,
InMemoryStore, mockEmbedder,
} from 'agentfootprint/memory';
import { anthropic, openai, bedrock, ollama, mock, mcpClient } from 'agentfootprint/providers';
import { withRetry, withFallback, fallbackProvider } from 'agentfootprint/resilience';
```
**Import each name from the door that exports it.** `package.json` `exports` is
exhaustive: a path that is not a door does not resolve, and a name imported from
a door that does not export it fails to compile (and is `undefined` under
`require`). The core API is on `agentfootprint`. LLM providers, embedders,
`mcpClient` and MCP transports such as `agentCoreRuntimeMcpTransport` are on
`agentfootprint/providers`. Memory, its stores included, is on
`agentfootprint/memory`, context engineering on `agentfootprint/context`,
resilience on `agentfootprint/resilience`, and observability on
`agentfootprint/observe`. Paths such as `agentfootprint/instructions` and
`agentfootprint/explain` do not exist.
## The 6-layer mental model
```
2 primitives : LLMCall · Agent (= ReAct)
3 compositions+Loop : Sequence · Parallel · Conditional · Loop
N patterns : ReAct · Reflexion · ToT · MapReduce · Debate · Swarm
(RECIPES, not classes)
Context engineering : defineSkill · defineSteering · defineInstruction · defineFact
Memory : defineMemory({type, strategy, store}) — 4 types × 7 strategies
Production features : pause/resume · cost · permissions · observability · events
```
## Three slots of the LLM API call
Every "agent feature" is content flowing into one of:
- `system` prompt — Steering / Instruction / Skill body / Fact / formatted memory
- `messages` array — history / RAG / memory replay / injected instructions
- `tools` array — Tool schemas (registered + Skill-attached)
## Mock-first development (RECOMMENDED)
Build with `mock()` provider + `InMemoryStore` + `mockEmbedder()` + inline tool closures first; swap real infra (Anthropic / OpenAI / Bedrock / Redis / Pinecone / real MCP) after the flow is right. Default code suggestions to mocks unless the user has set up keys/endpoints.
| Mock | Production swap |
|---|---|
| `mock({ reply })` · `mock({ replies })` for scripted multi-turn | `ollama('<model>')` — a real model, still $0 and no key · then `anthropic()` / `openai()` / `bedrock()` |
| `InMemoryStore` | `RedisStore` (`agentfootprint/memory`) · `AgentCoreStore` (`agentfootprint/memory`) · Dynamo · Postgres · Pinecone (planned) |
| `mockEmbedder()` | OpenAI / Cohere / Bedrock embedder factory |
| `mockMcpClient({ tools })` — in-memory, no SDK | `mcpClient({ transport })` real server |
## Canonical examples
### Hello agent — mock-first
```typescript
import { Agent } from 'agentfootprint';
import { mock } from 'agentfootprint/providers';
const agent = Agent.create({ provider: mock({ reply: 'San Francisco: 72°F.' }), model: 'mock' })
.system('You are a helpful assistant.')
.tool(weatherTool)
.build();
const result = await agent.run({ message: 'Weather in SF?' });
```
To swap to a real provider, change ONE line:
```typescript
provider: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }),
model: 'claude-sonnet-4-5-20250929',
```
### Tools
```typescript
const weather = defineTool({
schema: {
name: 'weather',
description: 'Current weather for a city.',
inputSchema: {
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
},
},
execute: async (args) => `${(args as { city: string }).city}: 72°F`,
});
```
### Context engineering
```typescript
const tone = defineSteering({ id: 'tone', prompt: 'Be friendly.' });
const urgent = defineInstruction({
id: 'urgent',
activeWhen: (ctx) => /urgent/i.test(ctx.userMessage),
prompt: 'Prioritize the fastest path.',
});
const billing = defineSkill({
id: 'billing',
description: 'Use for refunds.',
body: 'Confirm identity first.',
tools: [refundTool],
});
const userProfile = defineFact({ id: 'user', data: 'Alice, Pro plan.' });
agent.steering(tone).instruction(urgent).skill(billing).fact(userProfile);
```
### Memory
```typescript
const memory = defineMemory({
id: 'short-term',
type: MEMORY_TYPES.EPISODIC,
strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 },
store: new InMemoryStore(),
});
agent.memory(memory);
await agent.run({
message: '...',
identity: { tenant: 'acme', principal: 'alice', conversationId: 'thread-42' },
});
```
Types: `EPISODIC` · `SEMANTIC` · `NARRATIVE` · `CAUSAL` (snapshot replay ⭐).
Strategies: `WINDOW` · `BUDGET` · `SUMMARIZE` · `TOP_K` · `EXTRACT` · `DECAY` · `HYBRID`.
### MCP (Model Context Protocol — connect to external MCP servers)
```typescript
import { Agent } from 'agentfootprint';
import { mcpClient } from 'agentfootprint/providers';
const slack = await mcpClient({
name: 'slack',
transport: { transport: 'stdio', command: 'npx', args: ['@example/slack-mcp'] },
});
const agent = Agent.create({ provider })
.tools(await slack.tools()) // bulk-register all server tools
.build();
await slack.close();
```
Transports: `stdio` (local subprocess), `http` (Streamable HTTP). The
`@modelcontextprotocol/sdk` peer-dep is lazy-required — install it
when you actually use MCP, not before. `agent.tools(arr)` is the
bulk-register companion to `agent.tool(t)`.
### RAG (retrieval-augmented generation)
```typescript
import { defineRAG, indexDocuments } from 'agentfootprint';
const store = new InMemoryStore();
const embedder = mockEmbedder();
await indexDocuments(store, embedder, [
{ id: 'doc1', content: 'Refunds processed in 3 business days.' },
{ id: 'doc2', content: 'Pro plan: $20/month.' },
]);
const docs = defineRAG({ id: 'docs', store, embedder, topK: 3, threshold: 0.7 });
agent.rag(docs); // alias for .memory(), same plumbing
```
`defineRAG` is sugar over `defineMemory({ type: SEMANTIC, strategy: TOP_K })` with RAG-friendly defaults (topK=3, threshold=0.7). Same engine, clearer intent.
### Multi-agent via control flow (no `MultiAgentSystem` class)
```typescript
const pipeline = Sequence.create()
.step(researcher).step(writer).step(editor)
.build();
const tot = Parallel.create()
.branch(thoughtAgent).branch(thoughtAgent).branch(thoughtAgent)
.merge(rankerLLM)
.build();
```
## Anti-patterns — Don't
- ❌ `agent.run('string')` → use `agent.run({ message: '...', identity? })`
- ❌ Paths that are not doors: `agentfootprint/instructions`, `agentfootprint/explain` (see `package.json` `exports`)
- ❌ `.memoryPipeline(pipeline)` (v1) → use `.memory(defineMemory({...}))`
- ❌ Shipping a `ReflexionAgent` class → compose `Sequence(Agent, critique-LLM, Agent)`
- ❌ Closures or class instances in scope (can't be cloned)
- ❌ Falling back when TopK threshold returns nothing (strict semantics)
## Decision tree
| Goal | Use |
|---|---|
| One-shot LLM call | `LLMCall` |
| Loop with tools | `Agent` |
| Two LLM calls in series | `Sequence` |
| Multiple critics → merge | `Parallel` |
| Route by intent | `Conditional` |
| Iterate until quality bar | `Loop` |
| Output format / persona / safety | `defineSteering` |
| Rule-gated context | `defineInstruction` |
| LLM-activated body + tools | `defineSkill` |
| User profile / env data | `defineFact` |
| Last N turns | `defineMemory({ type: EPISODIC, strategy: WINDOW })` |
| Vector retrieval | `defineMemory({ type: SEMANTIC, strategy: TOP_K })` |
| Cross-run "why?" replay | `defineMemory({ type: CAUSAL, strategy: TOP_K })` |
| Long convo overflow | `defineMemory({ type: EPISODIC, strategy: SUMMARIZE })` |
When in doubt — read [`examples/`](examples/) (33 runnable specs).