n4nAI

Prompting Claude Opus 4.5 for long-horizon agent tasks

Step-by-step guide to prompting Claude Opus 4.5 agents for long-horizon tasks: state schemas, tool contracts, loop control, and verification that holds.

n4n Team3 min read654 words

Audio narration

Coming soon — every post will get a voice note here.

Long-horizon agent tasks break naive prompts. Effective prompting Claude Opus 4.5 agents requires explicit state contracts, strict tool schemas, and loop discipline; without them the model drifts after a dozen steps and silently drops subgoals. This guide gives a concrete pattern you can ship for multi-step automation that survives hundreds of iterations.

Step 1: Define a state schema and system prompt contract

Claude Opus 4.5 does not magically remember what it did 40 tool calls ago. You must force it to externalize state in a structured blob that you echo back each turn. Treat the state object as the only source of truth.

{
  "goal": "Migrate user data from SQLite to Postgres",
  "plan": [
    {"id": 1, "task": "Dump SQLite schema", "status": "done"},
    {"id": 2, "task": "Map types to PG", "status": "in_progress"},
    {"id": 3, "task": "Write migration script", "status": "pending"}
  ],
  "context": {"source_file": "app.db", "target_db": "postgres://..."},
  "errors": []
}

Your system prompt should state the invariants directly:

You are a stateful agent. After every action, return the full updated state object.
Never omit fields. Mark a plan item "done" only after verifying the action succeeded.
If a tool fails, append the error to "errors" and add a recovery plan item.

That single rule eliminates most goal-loss bugs. The model stops improvising hidden state in its latent space and puts everything where your driver can inspect it.

Step 2: Decompose the objective into a tracked plan

Prompting Claude Opus 4.5 agents works best when the model maintains its own checklist. Instruct it to expand vague goals into concrete steps at the start, then tick them off as it acts.

Plan length limits

Empirically, plans longer than 10 items cause the model to truncate or loop. Force chunking: when the plan reaches 8 active items, instruct it to summarize completed work and start a fresh plan with a new root goal.

SYSTEM = """You are an agent. Given a goal, first produce a plan of <=10 steps.
After each tool call, update plan status. If plan length hits 8, compress
completed items into context and start a new plan. Output state as valid JSON."""

Dynamic replanning

If a step fails twice, the model should spawn an alternative approach as a new plan item rather than retrying identically. This prevents burn loops that waste tokens and time.

Step 3: Specify tool interfaces with strict schemas

Vague tool descriptions waste tokens and invite malformed calls. Define each tool with a JSON schema and a single responsibility. Below is a minimal Python tool registration using the OpenAI-compatible function format.

tools = [
  {
    "type": "function",
    "function": {
      "name": "run_sql",
      "description": "Execute a read-only SQL statement against the source DB",
      "parameters": {
        "type": "object",
        "properties": {
          "query": {"type": "string"},
          "limit": {"type": "integer", "default": 100}
        },
        "required": ["query"]
      }
    }
  },
  {
    "type": "function",
    "function": {
      "name": "exec_script",
      "description": "Run a Python migration script and return stdout/stderr",
      "parameters": {
        "type": "object",
        "properties": {"path": {"type": "string"}},
        "required": ["path"]
      }
    }
  }
]

Tell the model: “Call exactly one tool per turn. Never invent parameters outside the schema.” This prevents the classic hallucinated-argument loop that hangs an agent.

Step 4: Implement the agent loop with explicit stop conditions

A long-horizon run needs a driver that enforces termination. Below is a minimal loop. It stops when the plan is all done or after a max steps guard.

from openai import OpenAI
import json

client = OpenAI(base_url="https://YOUR_GATEWAY/v1", api_key="KEY")

messages = [
    {"role": "system", "content": SYSTEM},
    {"role": "user", "content": "Migrate app.db to Postgres"}
]

MAX_STEPS = 200
for step in range(MAX_STEPS):
    resp = client.chat.completions.create(
        model="claude-opus-4.5",
        messages=messages,
        tools=tools,
        tool_choice="auto"
    )
    msg = resp.choices[0].message
    if msg.tool_calls:
        for tc in msg.tool_calls:
            args = json.loads(tc.function.arguments)
            result = dispatch(tc.function.name, args)
            messages.append({
                "role": "tool",
                "tool_call_id": tc.id,
                "content": json.dumps(result)
            })
    else:
        state = json.loads(msg.content)
        if all(p["status"] == "done" for p in state["plan"]):
            print("Goal complete at step", step)
            break
    messages.append({"role": "assistant", "content": msg.content or ""})
else:
    raise RuntimeError("Exceeded MAX_STEPS without completion")

Tool result shaping

Keep tool results small. Return only what the next decision needs, not raw dumps. Large results bloat context and trigger silent truncation in later steps.

Step 5: Inject memory and recovery directives

Context windows are finite. Every 20 steps, compress the context field: drop raw outputs, keep conclusions.

if step % 20 == 0 and step > 0:
    messages.append({
        "role": "system",
        "content": "Compress context: keep only decisions and schema mappings, drop raw rows."
    })

Error recovery

Add a standing directive: “If you see ‘RECOVER’ in errors, revert the last plan item to pending and try an alternative.” The model then self-heals instead of cascading failures.

This pattern keeps prompting Claude Opus 4.5 agents stable across long runs because it mimics a human sprint retrospective—regular compaction and redirection.

Step 6: Verify with execution traces

Success is not “the model said done.” You verify by replaying the plan and checking side effects. After the loop, assert:

assert state["goal"] == "Migrate user data from SQLite to Postgres"
assert not state["errors"]
assert all(p["status"] == "done" for p in state["plan"])

# Side-effect check: actual row count in target
src_count = dispatch("run_sql", {"query": "SELECT COUNT(*) FROM users"})["count"]
tgt_count = dispatch("run_pg", {"query": "SELECT COUNT(*) FROM users"})["count"]
assert src_count == tgt_count, "Data loss detected"

Log the full messages array to inspect where the model deviated. Trace analysis shows most failures cluster at plan boundaries—exactly where your compression step runs.

Step 7: Route for resilience (optional)

Provider degradation is real. n4n.ai provides one OpenAI-compatible endpoint addressing 240+ models; its automatic fallback switches to a secondary provider when Anthropic is rate-limited or degraded, and it honors client routing directives and forwards provider cache-control hints. Per-token usage metering lets you spot runs that ballooned context via redundant tool calls. The prompt design above stays identical—only the base_url changes.

Verification checklist

  • State JSON validates against schema every turn
  • Plan items transition pendingin_progressdone with no skips
  • Tool calls match schema; no unknown params
  • Loop terminated via all done, not max steps
  • Side-effect check confirms goal achieved (e.g., row counts match)

Prompting Claude Opus 4.5 agents for long horizons is mostly about removing ambiguity from the contract. Do that, and the model handles the messy middle reliably.

Tagsclaude-opus-4-5prompt-engineeringai-agentslong-horizon-tasks

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All prompt engineering for agentic systems posts →