Instructions
A user types “my refund still hasn’t arrived I’m furious”. You want the agent to acknowledge feelings before facts — but only on this kind of message, not every message. That’s an Instruction. A predicate matches the iteration; the matching prompt joins the system slot for this turn only. No global state to drift, no separate “rules engine” to maintain.
What an Instruction is
Section titled “What an Instruction is”An Instruction is one flavor of the Injection primitive — content that lands in a slot when a trigger matches. For Instructions specifically:
- Slot:
system-prompt(default) ormessages - Trigger:
rule— a predicate(ctx) => booleanyou write - Fires: every iteration of the agent loop, against fresh context
If the predicate returns true, the prompt text is appended to the system slot for that iteration. If it returns false, nothing happens. The predicate re-runs every iteration so the same Instruction can activate on iteration 3 but not on iteration 5 — context is fresh each time.
Defining an Instruction
Section titled “Defining an Instruction”defineInstruction takes an id, a prompt, and an activeWhen predicate. The predicate receives an InjectionContext with the current userMessage, iteration count, lastToolResult, activated skills, and conversation history:
const calmTone = defineInstruction({ id: 'calm-tone', description: 'Calm, empathetic tone with frustrated users.', activeWhen: (ctx) => /upset|angry|frustrated/i.test(ctx.userMessage), prompt: 'The user sounds upset. Acknowledge feelings before facts. Avoid corporate jargon.',});
const concise = defineInstruction({ id: 'concise', activeWhen: (ctx) => ctx.iteration === 1, // first iteration only prompt: 'Keep your first response under 3 sentences.',});Two instructions, two predicates. calmTone activates when the user’s message contains an upset-sounding word. concise activates only on the first iteration (so follow-up turns can be longer if they need to be). Predicates are pure functions — no side effects, no async, no IO. They run dozens of times per agent.run().
Attaching to an Agent
Section titled “Attaching to an Agent”Once defined, attach with .instruction(...). Multiple instructions stack — each runs its own predicate; matches all land in the system slot in registration order:
const agent = Agent.create({ provider: provider ?? mock({ reply: 'I hear you. Let me help.' }), model: 'mock', maxIterations: 1,}) .system('You are a customer support assistant.') .instruction(calmTone) .instruction(concise) .build();The agent doesn’t know there are “instructions” attached — it just sees a system prompt that varies by turn. The agentfootprint.context.injected event fires with source: 'instruction' and the matching id so observability surfaces can show which rules fired when.
The on-tool-return trigger (Dynamic ReAct)
Section titled “The on-tool-return trigger (Dynamic ReAct)”An Instruction whose predicate inspects ctx.lastToolResult is naturally one-shot — fires on the iteration RIGHT AFTER the named tool ran, then the predicate stops matching on the next iteration because lastToolResult will be from a different (or no) tool. This is the on-tool-return trigger pattern from the 4-trigger taxonomy:
const postPii = defineInstruction({ id: 'post-pii', description: 'Brief reminder to use the redacted text, not the original.', activeWhen: (ctx) => ctx.lastToolResult?.toolName === 'redact_pii', prompt: 'Use the redacted text in your reply. Do not paraphrase the original.',});The reminder lands ONLY on the iteration where the LLM is about to read the redacted output. Without this, the LLM sometimes paraphrases the original (defeating the redaction). With it, the LLM is told “use the redacted text” at the exact moment it needs to hear it. Modern LLMs attend more strongly to recent messages than to the system prompt, so injecting at this position is structurally better than baking the same advice into the always-on system prompt.
This is the Dynamic ReAct pattern from Shinn 2023’s reflection paper — context that adapts mid-loop based on what the agent just observed.
Where the Instruction lands — slot choice
Section titled “Where the Instruction lands — slot choice”By default the Instruction’s prompt joins the system slot. For instructions that should land with higher attention recency (the “use redacted text” example above), set slot: 'messages' so the content appears alongside fresh tool results in the message history rather than buried in the long system prompt:
defineInstruction({ id: 'critical-after-tool', activeWhen: (ctx) => ctx.lastToolResult?.toolName === 'critical_op', prompt: '...', slot: 'messages', // override default; lands as a synthetic system message in messages array});When to use which:
slot: 'system'(default) — baseline behavior shaping (tone, format, persona). Provider attention to system prompts varies but is reliable on Claude/GPT-4.slot: 'messages'— recency-first. Use when the rule MUST be honored and you suspect long-context attention decay would weaken a system-prompt placement.
Anti-patterns
Section titled “Anti-patterns”- ❌ Don’t put dynamic state in the predicate’s closure — it’s evaluated per iteration, with fresh
ctx. Reading fromctxis correct; capturing alet counterin the closure is racy. - ❌ Don’t make
activeWhenasync or side-effecting — it runs many times per turn; latency multiplies. - ❌ Don’t combine many predicates into one giant Instruction — register multiple small ones with single-purpose predicates. Easier to reason about, easier to observe (one event per matching id).
- ❌ Don’t rely on Instruction order for correctness — registration order determines the order of matching prompts in the system slot, but the LLM doesn’t strictly read top-to-bottom. If two Instructions could conflict, write one Instruction with the conflict resolved in its prompt.
Next steps
Section titled “Next steps”- Skills, explained — context engineering for instructions, taken further (LLM-activated skills + tools)
- Memory guide — for stateful context across runs (Instructions are per-iteration; Memory is cross-run)
- Where to land an injection — slot × trigger × per-provider attention guidance (coming in v2.4)