---
title: Instructions
description: Rule-gated context injection. The Instruction primitive activates a prompt when a predicate matches the current iteration's context.
---

import CodeFile from '@/components/CodeFile.astro';

> 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

An **Instruction** is one flavor of the [Injection primitive](/agentfootprint/getting-started/key-concepts/) — content that lands in a slot when a trigger matches. For Instructions specifically:

- **Slot:** `system-prompt` (default) or `messages`
- **Trigger:** `rule` — a predicate `(ctx) => boolean` you 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

`defineInstruction` takes an `id`, a `prompt`, and an `activeWhen` predicate. The predicate receives an `InjectionContext` with the current `userMessage`, `iteration` count, `lastToolResult`, `activatedInjectionIds` (ids of skills/injections the LLM activated this turn), and conversation `history`:

<CodeFile path="examples/context-engineering/01-instruction.ts" region="define" title="examples/context-engineering/01-instruction.ts (region: define)" />

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

Once defined, attach with `.instruction(...)`. Multiple instructions stack — each runs its own predicate; matches all land in the system slot in registration order:

<CodeFile path="examples/context-engineering/01-instruction.ts" region="attach" title="examples/context-engineering/01-instruction.ts (region: attach)" />

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: 'instructions'` and the matching `id` so observability surfaces can show which rules fired when.

## 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:

<CodeFile path="examples/context-engineering/05-dynamic-react.ts" region="on-tool-return" title="examples/context-engineering/05-dynamic-react.ts (region: on-tool-return)" />

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

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:

```ts
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-prompt'` (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. The injected text lands as a synthetic message with `role: 'system'` by default; pass `role: 'user'` (the other commonly-useful `ContextRole`) to land it as a user-turn message instead.

## Anti-patterns

- ❌ **Don't put dynamic state in the predicate's closure** — it's evaluated per iteration, with fresh `ctx`. Reading from `ctx` is correct; capturing a `let counter` in the closure is racy.
- ❌ **Don't make `activeWhen` async 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

- [Skills, explained](/agentfootprint/guides/skills-explained/) — context engineering for instructions, taken further (LLM-activated skills + tools)
- [Memory guide](/agentfootprint/guides/memory/) — for stateful context across runs (Instructions are per-iteration; Memory is cross-run)
- [Key concepts](/agentfootprint/getting-started/key-concepts/) — the Injection primitive (`slot × trigger × cache`) and how every flavor reduces to it
