Prompt to Harness
Prompt, context, loop, harness. The code for each.
Four names for four layers of the same work. Each one sits inside the next.
Layer 1
Prompt engineering
The words the model reads. Here they don’t have to be one long string.
Each rule has a name, and it applies only when its condition holds. In this sample a calm message never carries the rule about tone, and an upset one does.
Instructionsimport { Agent } from 'agentfootprint';import { defineInstruction } from 'agentfootprint/context'; const calmTone = defineInstruction({ id: 'calm-tone', activeWhen: (ctx) => /upset|angry|frustrated/i.test(ctx.userMessage), prompt: 'The user sounds upset. Acknowledge feelings before facts.',}); const agent = Agent.create({ provider, model: 'mock' }) .system('You answer refund questions.') .instruction(calmTone) .build();Layer 2
Context engineering
What the model gets to see, each time it’s called.
Steering, facts, skills, memory and retrieved documents each land in the system prompt, the messages or the tools, at a moment you choose. The context is rebuilt on every iteration, and you can list what landed and where.
Key conceptsimport { Agent, contextEngineering } from 'agentfootprint';import { defineFact, defineSkill, defineSteering } from 'agentfootprint/context';import { InMemoryStore, MEMORY_STRATEGIES, MEMORY_TYPES, defineMemory } from 'agentfootprint/memory'; const agent = Agent.create({ provider, model: 'mock' }) .system('You answer refund questions.') .steering(defineSteering({ id: 'tone', prompt: 'Be brief and kind.' })) .fact(defineFact({ id: 'plan', data: 'Customer plan: Pro.' })) .skill(defineSkill({ id: 'refunds', description: 'Refund rules.', body: 'Check the ledger first.' })) .memory( defineMemory({ id: 'recent', type: MEMORY_TYPES.EPISODIC, strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 }, store: new InMemoryStore(), }), ) .build(); // Every piece that lands, and where it landed, on every iteration.contextEngineering(agent).onEngineered((event) => { landed.push(`${event.payload.source} → ${event.payload.slot}`);});Layer 3
Loop engineering
What happens between model calls.
A turn has five moments where your rules can act. A rule can rewrite what passes through or refuse a call, and it can’t invent a tool’s result, so what the model reads is the real output or a refusal.
The moments of the loopimport { Agent, allow, deny, slidingWindow, type MessageMiddleware, type ToolMiddleware } from 'agentfootprint'; const scrubCards: MessageMiddleware = { name: 'scrub-cards', onMessage: (message) => { const clean = message.content.replace(/\b\d{4}[ -]\d{4}\b/g, '[card]'); return clean === message.content ? allow() : allow(clean, 'masked a card number'); },}; const knownOrdersOnly: ToolMiddleware = { name: 'known-orders-only', onToolCall: (call) => (/^A-\d{4}$/.test(String(call.args.orderId)) ? allow() : deny('order ids look like A-1042')),}; const dropInternalNotes: ToolMiddleware = { name: 'drop-internal-notes', onToolResult: (call) => { const { internalNote: _hidden, ...safe } = call.result as Record<string, unknown>; return allow(safe, 'removed the internal note'); },}; const noCodenames: MessageMiddleware = { name: 'no-codenames', onMessage: (message) => { const clean = message.content.replace(/PROJECT-BLUEJAY/g, 'our review team'); return clean === message.content ? allow() : allow(clean, 'internal codename'); },}; const agent = Agent.create({ provider, model: 'mock' }) .tool(getRefund) .act({ input: [scrubCards], // the message, before the run commits it beforeTool: [knownOrdersOnly], // every call, before it is dispatched afterTool: [dropInternalNotes], // every result, before the model reads it window: slidingWindow({ keepRecentTurns: 12 }), // what the model still sees output: [noCodenames], // the answer, before the caller gets it }) .build();Re-run it on a timer
Some people use loop engineering for a system that prompts, checks, remembers and re-runs an agent on a timer.Loop does the re-running. The timer is yours, because the library doesn’t run a scheduler.
import { Loop } from 'agentfootprint'; // Re-run until your check passes, five times at most.const nightly = Loop.create({ name: 'NightlyCheck' }) .repeat(agent) .times(5) .until(({ latestOutput }) => passesMyCheck(latestOutput)) .build(); // The timer is yours: cron, EventBridge or Cloud Scheduler. Each firing calls this.export const onSchedule = () => nightly.run({ message: 'Check overnight refunds.' });Layer 4
Harness engineering
Everything around the model. The shorthand is agent = model + harness: the loop, tools, context and controls.
Here the harness is your code, not a product you log in to. So the prompt, the tools, the rules and the proof sit together in one place you can read and test.
Demo to Appimport { Agent } from 'agentfootprint';import { auditExport } from 'agentfootprint/observe';import { withFallback } from 'agentfootprint/resilience';import { PermissionPolicy, conversationRedaction } from 'agentfootprint/security'; const roles = { support: ['get_refund', 'list_skills', 'read_skill'] }; const agent = Agent.create({ provider: withFallback(primary, backup), // stay up when a provider is down model: 'mock', permissionChecker: PermissionPolicy.fromRoles(roles, 'support'), // who can call what redact: conversationRedaction(), // what the record keeps}) .system('You answer refund questions.') // the prompt .tool(getRefund) // the tools .act({ beforeTool: [knownOrdersOnly] }) // the loop .selfExplain() // it can read its own record .build(); const audit = auditExport({ agent: 'refunds' }); // proof, afterwardsagent.enable.observability({ strategy: audit });