Prompts & Agent SOPs

Lesson 4 · Strands Agents (TypeScript) · ~10 minutes

You've built agents with tools, hooks, and state. Now the question is: how do you make them predictable? With a powerful model like Claude Opus, you can get away with vague instructions. With Sonnet 4.6 — your cost-conscious default — you need precision. This lesson covers the hierarchy of steering mechanisms, from basic system prompts to Agent SOPs that give you deterministic-ish behavior without sacrificing the model's reasoning ability.

System Prompts: The Persistent Instruction Set

The system prompt is passed to the model on every turn of the agent loop. It defines the agent's role, capabilities, and constraints. In Strands, you set it via the systemPrompt option on the Agent constructor:

import { Agent } from '@strands-agents/sdk'

const agent = new Agent({
  model: 'us.anthropic.claude-sonnet-4-6-20250725-v1:0',
  systemPrompt: `You are a code reviewer for TypeScript projects.

Your role:
- Review code for correctness, readability, and performance
- Flag security issues immediately
- Suggest concrete improvements with code examples

Constraints:
- Never approve code with any TODO comments
- Always check for proper error handling
- Respond in markdown format`,
  tools: [/* ... */],
})

The system prompt is your first determinism lever. Sonnet 4.6 follows explicit instructions faithfully — the more specific you are, the more predictable the output. Compare:

Vague (non-deterministic)Explicit (deterministic)
"Review this code""Check for: 1) unused imports 2) missing error handling 3) type safety issues. Output as a numbered list."
"Help with the API""Generate a REST endpoint handler. Use express. Return JSON. Include input validation with zod."
"Analyze the logs""Extract all ERROR-level entries from the last 24h. Group by service. Count occurrences. Output as a markdown table."
Determinism principle

Every decision you leave implicit is a decision the model makes non-deterministically. Sonnet 4.6 is capable but literal — it does what you say, not what you mean. Spell out format, order, constraints, and edge-case handling.

User Messages: Text and Multi-Modal

The system prompt is persistent context. User messages are the per-invocation input. Three patterns:

Plain text

// Simple text input — most common
const result = await agent.invoke('Review this function for security issues')

Multi-modal (text + images)

import { TextBlock, ImageBlock } from '@strands-agents/sdk'

// Send text alongside an image (e.g., a screenshot of a UI bug)
const result = await agent.invoke([
  new TextBlock('What accessibility issues do you see in this UI?'),
  new ImageBlock({
    source: { type: 'base64', mediaType: 'image/png', data: screenshotBase64 },
  }),
])

Pre-filling conversation history

You can seed the agent with prior context via the messages option. This is useful for multi-turn workflows where you're reconstructing a session:

const result = await agent.invoke('Now implement the changes we discussed', {
  messages: [
    { role: 'user', content: 'Review src/auth.ts for security issues' },
    { role: 'assistant', content: 'I found 3 issues: 1) No rate limiting...' },
    { role: 'user', content: 'Fix issues 1 and 3, skip issue 2' },
  ],
})

The model sees the full history as context — it "remembers" the prior conversation. This is how you build stateful multi-step workflows without keeping the agent alive between requests.

The Determinism Spectrum

There's a spectrum between fully coded logic and fully open-ended agents:

Hardcoded
if/else chains
Agent SOPs
structured freedom
Open-ended
"figure it out"

Hardcoded logic is perfectly deterministic but can't handle novel inputs. Open-ended agents handle anything but produce unpredictable outputs. Agent SOPs hit the sweet spot: you define the process (steps, constraints, decision points) while the model handles the reasoning within each step.

Agent SOPs: Structured Workflows in Natural Language

An Agent SOP is a markdown document that defines a structured workflow for an agent. It uses RFC 2119 keywords (MUST, SHOULD, MAY) to specify constraints at each step. The agent follows the SOP like a checklist — but retains the ability to reason about edge cases within each step.

Why SOPs matter

SOP structure

Anatomy of an Agent SOP

# [Agent Name] SOP

## Parameters
- `input_param`: description and type
- `output_param`: what this agent produces

## Steps

### Step 1: [Name]
- MUST [hard constraint — violation is a bug]
- SHOULD [strong preference — deviate only with justification]
- MAY [optional behavior — agent decides based on context]

### Step 2: [Name]
- MUST [...]
- If [condition], MUST [branch behavior]

## Output Format
- MUST return [structured format description]
- MUST NOT include [excluded content]

Example: Code Review SOP

const CODE_REVIEW_SOP = `
# Code Review Agent SOP

## Parameters
- \`diff\`: The git diff to review (string)
- \`language\`: Programming language of the changed files

## Steps

### Step 1: Classify Changes
- MUST categorize each changed file as: feature, bugfix, refactor, test, or config
- MUST identify the primary intent of the PR in one sentence
- MUST NOT proceed if the diff is empty

### Step 2: Security Scan
- MUST check for: hardcoded secrets, SQL injection, XSS vectors, auth bypasses
- MUST flag any finding as severity: critical, high, medium, low
- If critical findings exist, MUST stop and report immediately

### Step 3: Logic Review
- MUST verify error handling for all external calls
- MUST check that new branches have corresponding tests
- SHOULD flag functions over 50 lines
- MAY suggest alternative implementations if significantly simpler

### Step 4: Style Check
- MUST verify consistent naming conventions
- SHOULD flag magic numbers without named constants
- MUST NOT block on purely cosmetic issues

## Output Format
- MUST return a JSON object with: summary, findings[], verdict (approve|request_changes|block)
- Each finding MUST have: file, line, severity, message, suggestion
- MUST NOT include praise or filler text
`

const reviewer = new Agent({
  model: 'us.anthropic.claude-sonnet-4-6-20250725-v1:0',
  systemPrompt: CODE_REVIEW_SOP,
  tools: [readFileTool, searchCodeTool],
})

The SOP tells Sonnet 4.6 exactly what process to follow, what's mandatory versus optional, and what format to produce. The model still reasons — it decides what to flag — but the how is constrained.

Practical Pattern: API Endpoint Generator

Here's a production-grade SOP for a consultant's bread-and-butter task: generating API endpoints from a spec. The agent follows a fixed process but reasons about the domain-specific details.

const API_GENERATOR_SOP = `
# API Endpoint Generator SOP

## Parameters
- \`spec\`: OpenAPI path object or natural language endpoint description
- \`framework\`: Target framework (express | fastify | hono)
- \`outputDir\`: Directory to write generated files

## Steps

### Step 1: Parse Specification
- MUST extract: HTTP method, path, request body schema, response schema, auth requirements
- MUST identify path parameters and query parameters with their types
- If spec is ambiguous, MUST ask for clarification before proceeding
- MUST NOT invent fields not present in the spec

### Step 2: Scaffold Files
- MUST create these files:
  - \`{outputDir}/handler.ts\` — the route handler
  - \`{outputDir}/schema.ts\` — Zod schemas for request/response validation
  - \`{outputDir}/handler.test.ts\` — test file with happy path + error cases
- MUST use the project's existing patterns (check adjacent handlers for style)
- SHOULD reuse existing shared schemas if they match

### Step 3: Implement Handler
- MUST validate request body with the generated Zod schema
- MUST return proper HTTP status codes (201 for creation, 404 for not found, 422 for validation)
- MUST include error handling that returns structured error responses
- MUST NOT use any/unknown types — all inputs and outputs fully typed
- If auth is required, MUST use the project's auth middleware pattern
- MAY add request logging if the project uses a structured logger

### Step 4: Write Tests
- MUST test: happy path, validation failure, auth failure (if applicable), not-found case
- MUST use the project's test utilities (check conftest/setup files)
- SHOULD test edge cases specific to the business logic
- MUST NOT mock the validation layer — test it end-to-end

### Step 5: Verify
- MUST run TypeScript compilation (tsc --noEmit) on generated files
- MUST run tests and confirm all pass
- If compilation or tests fail, MUST fix before reporting done

## Output Format
- MUST report: files created, test results, any decisions made
- MUST flag any spec ambiguities that were resolved with assumptions
`

const apiGenerator = new Agent({
  model: 'us.anthropic.claude-sonnet-4-6-20250725-v1:0',
  systemPrompt: API_GENERATOR_SOP,
  tools: [readFileTool, writeFileTool, shellTool, searchTool],
  limits: { turns: 15 },
})

// Usage
const result = await apiGenerator.invoke(`
Generate endpoint from this spec:
POST /api/v1/invoices
Body: { clientId: string, lineItems: [{description: string, amount: number}], dueDate: ISO date }
Response: 201 with created invoice including generated ID and total
Auth: Bearer token required
Framework: hono
Output to: src/routes/invoices/create/
`)

This pattern scales to any repeatable task in your consulting work: database migrations, infrastructure modules, data pipeline steps, onboarding checklists. The SOP defines the process; the agent handles the reasoning.

SOP Design Principles

Writing effective SOPs for Sonnet 4.6 requires specific techniques:

PrincipleWhyExample
Steps are ordered Sequential execution is easier for the model to follow than parallel Parse → Scaffold → Implement → Test → Verify
MUST for invariants Sonnet treats MUST as non-negotiable — use it for correctness constraints "MUST validate input before processing"
SHOULD for preferences Gives the model flexibility when context makes the preference inapplicable "SHOULD reuse existing schemas if they match"
MAY for optionality Model decides based on context — preserves reasoning without mandating "MAY add logging if logger is configured"
Explicit edge cases Prevents the model from inventing behavior for unspecified scenarios "If spec is ambiguous, MUST ask — MUST NOT guess"
Output format at the end Anchors the model's final response structure "MUST return JSON with: summary, files, verdict"
Key Insight

SOPs are the primary determinism tool for simpler models. They constrain the model's decision space without removing its reasoning ability. A well-written SOP with Sonnet 4.6 will produce more consistent output than a vague prompt with Opus — at a fraction of the cost.

Combining Prompts: System + SOP + User Input

In practice, the full prompt stack looks like this:

┌─────────────────────────────────────────────────────┐ │ System Prompt (SOP) │ │ - Role, capabilities, constraints │ │ - Step-by-step procedure with MUST/SHOULD/MAY │ │ - Output format specification │ ├─────────────────────────────────────────────────────┤ │ Conversation History (messages option) │ │ - Prior context if multi-turn │ │ - Pre-filled examples for few-shot │ ├─────────────────────────────────────────────────────┤ │ User Message (invoke argument) │ │ - The specific task instance │ │ - Parameters the SOP expects │ └─────────────────────────────────────────────────────┘

The SOP lives in the system prompt. The user message provides the specific instance data. The conversation history provides continuity across turns. Together, they form a complete instruction set that's both structured and flexible.

// Full pattern: SOP + few-shot examples + task
const agent = new Agent({
  model: 'us.anthropic.claude-sonnet-4-6-20250725-v1:0',
  systemPrompt: API_GENERATOR_SOP,
  tools: [readFileTool, writeFileTool, shellTool],
})

// Few-shot via messages option — shows the agent a completed example
const result = await agent.invoke('Generate POST /api/v1/payments endpoint...', {
  messages: [
    { role: 'user', content: 'Generate GET /api/v1/users/:id endpoint...' },
    { role: 'assistant', content: 'Step 1: Parsed spec...\nStep 2: Created files...\n...' },
  ],
})

When SOPs Aren't Enough

SOPs work best for tasks with a repeatable process. They're less effective for:

For these cases, combine SOPs with multi-agent orchestration (next lesson) — a coordinator agent with a loose SOP delegates to specialist agents with tight SOPs.

You have an agent that sometimes returns results as a markdown table and sometimes as a bulleted list. Which approach best fixes this with Sonnet 4.6?
Correct. The non-determinism comes from an underspecified output format. A MUST constraint in the SOP eliminates the ambiguity — Sonnet 4.6 follows explicit format instructions reliably. Temperature=0 reduces randomness in token selection but doesn't solve format ambiguity.
The right fix is adding an explicit format constraint. "MUST return results as a markdown table with columns: Name, Status, Details" eliminates the ambiguity at the source. Temperature affects token sampling randomness but doesn't help when the model has two equally valid interpretations of an underspecified instruction.
What's the key difference between using MUST vs SHOULD in an Agent SOP?
Correct. MUST defines hard constraints — the agent treats them as non-negotiable rules. SHOULD defines strong preferences that the agent can deviate from when the specific context makes them inapplicable. This gives you determinism on the things that matter (correctness, security, format) while preserving flexibility where reasoning adds value.
The distinction is about constraint strength. MUST is an invariant — violation means the agent is broken. SHOULD is a strong preference — the agent can deviate when context justifies it (e.g., "SHOULD reuse existing schemas" doesn't apply if no existing schemas match). There's no runtime error/warning mechanism — it's about how the model interprets the instruction.
📖 Primary Source

Prompts — Strands Agents Docs — Official documentation on system prompts, user messages, multi-modal input, and conversation history management.

💬 Questions? Ask me about SOP design patterns, how to debug non-deterministic agent behavior, or how to structure prompts for your specific use case.
← Back Next →