Context engineering recorder
Your agent emits a
context.injectedevent every time content lands in any slot — including the user’s message, every tool result, the static system prompt anchor. For Lens UIs, evals, cost attribution, debug logging, the baseline flow is noise.contextEngineering(agent)gives you two filtered subscriptions: just-engineered + just-baseline. Same events, cleaner streams.
What it does
Section titled “What it does”import { Agent, contextEngineering } from 'agentfootprint';
const agent = Agent.create({...}) .system('You answer questions.') .instruction(beFriendly) .skills(supportRegistry) .memory(recentMemory) .build();
const ce = contextEngineering(agent);
// Just the engineered injectionsce.onEngineered((e) => { console.log(`[${e.payload.source}] ${e.payload.contentSummary}`); // → [skill] billing skill body // → [memory] last 3 user turns // → [instructions] be-friendly rule});
// Just the baseline message-history flowce.onBaseline((e) => { console.log(`[baseline:${e.payload.source}]`); // → [baseline:user] // → [baseline:tool-result]});
await agent.run({ message: 'help me with a refund' });ce.detach(); // remove all subscriptions when doneThe engineered / baseline split
Section titled “The engineered / baseline split”agentfootprint.context.injected events carry a source: ContextSource field. The library classifies sources into two disjoint sets:
| Engineered (the context-engineering work) | Baseline (the message flow) |
|---|---|
'rag' — retrieval-augmented chunks | 'user' — user input |
'skill' — activated skill bodies | 'tool-result' — tool returns |
'memory' — memory subsystem injections | 'assistant' — prior LLM turns |
'instructions' — rule-based guidance | 'base' — static system prompt anchor |
'steering' — always-on policy | 'registry' — static tool catalog |
'fact' — developer-supplied facts | |
'custom' — consumer-defined |
Two filter helpers expose the classifier as pure functions for ad-hoc use without the wrapper:
import { isEngineeredSource, isBaselineSource, ENGINEERED_SOURCES } from 'agentfootprint';
isEngineeredSource('rag'); // → trueisEngineeredSource('user'); // → falseisBaselineSource('tool-result'); // → true
[...ENGINEERED_SOURCES]; // → ['rag','skill','memory','instructions','steering','fact','custom']Use cases
Section titled “Use cases”1. Lens UI — render engineered injections in the “context bin”
Section titled “1. Lens UI — render engineered injections in the “context bin””Lens shows the engineered set as cards on the agent’s iteration timeline; the baseline flow appears as edges between iterations. Without contextEngineering, the UI would conflate “the user said hello” with “RAG retrieved 4 product-doc chunks” — visually noisy, semantically different.
2. Eval pipelines — what entered the prompt for this query?
Section titled “2. Eval pipelines — what entered the prompt for this query?”const counts: Record<string, number> = {};const ce = contextEngineering(agent);ce.onEngineered((e) => { counts[e.payload.source] = (counts[e.payload.source] ?? 0) + 1;});await agent.run({ message: evalQuery });ce.detach();// counts: { rag: 3, skill: 1, instructions: 2 } — eval row3. Cost attribution — token spend per engineered source
Section titled “3. Cost attribution — token spend per engineered source”const tokensBySource: Record<string, number> = {};ce.onEngineered((e) => { const t = e.payload.budgetSpent?.tokens ?? 0; tokensBySource[e.payload.source] = (tokensBySource[e.payload.source] ?? 0) + t;});Pair with costRecorder to know what fraction of spend was engineered vs baseline.
4. Debug logging — tail engineered signals during dev
Section titled “4. Debug logging — tail engineered signals during dev”ce.onEngineered((e) => { console.log(`[${e.payload.slot}/${e.payload.source}/${e.payload.sourceId}] ${e.payload.reason}`);});Spot surprising activations without drowning in user-message noise.
Forward-compat behavior — unknown sources
Section titled “Forward-compat behavior — unknown sources”The ContextSource union is open-extensible (new flavors can be added as a non-breaking change). When an event arrives with a source that’s in NEITHER ENGINEERED_SOURCES nor BASELINE_SOURCES, the wrapper routes it to NEITHER stream — preferring under-fire over miscategorization.
If your agent uses a forward-future source the library doesn’t yet know about, subscribe to the raw event for completeness:
agent.on('agentfootprint.context.injected', (e) => { if (!isEngineeredSource(e.payload.source) && !isBaselineSource(e.payload.source)) { // forward-future source — handle as you wish }});contextEngineering(agent) | Returns a ContextEngineeringHandle |
handle.onEngineered(cb) | Subscribe to engineered injections; returns unsub |
handle.onBaseline(cb) | Subscribe to baseline injections; returns unsub |
handle.detach() | Unsubscribes ALL listeners registered through the handle (idempotent) |
isEngineeredSource(source) | Pure classifier |
isBaselineSource(source) | Pure classifier |
ENGINEERED_SOURCES | ReadonlySet<ContextSource> |
BASELINE_SOURCES | ReadonlySet<ContextSource> |
The wrapper accepts any runner that implements agent.on('agentfootprint.context.injected', cb) — Agent, LLMCall, Sequence, Parallel, Conditional, Loop. Each runner emits its own context events; pass the right one.
Anti-patterns
Section titled “Anti-patterns”- Don’t subscribe via
contextEngineeringAND rawagent.on('agentfootprint.context.injected', ...)for the same purpose. The raw subscription fires for ALL sources; layering breaks the filter intent. Pick one. - Don’t forget to
detach()long-lived handles. EachonEngineered/onBaselineregisters a listener on the agent; without detach, listeners accumulate across runs and leak memory. - Don’t classify raw
ContextSourcestrings yourself. UseisEngineeredSource/isBaselineSourceso future additions to the union are routed consistently across consumers.
Next steps
Section titled “Next steps”- Observability guide — the broader event taxonomy
- Dynamic ReAct guide — what makes per-iteration recomposition produce a stream of engineered injections
- RAG, Memory, Skills, Instructions — the engineered-source primitives