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.

Instructions
import { 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 concepts
import { 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 loop
import { 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.

Compositions and patterns
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 App
import { 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 });