Build a task agent from the template with state, retry and termination¶
Draft outline: not validated
Drafted with AI from official sources on 16 Oct 2026; updated on 22 Oct 2026 for task AGT-01-T05. Do not use on a live system until it is written, dry-run by someone other than the author and signed off (planned 2026-12-08). Author: Sanjay Sarvesh · dry-run: Yasar · sign-off: James. Work items: AGT-01-T05, AGT-01-T06, AGT-01-T07, AGT-01-T08.
Template scope
This playbook builds the task agent template only. The task agent is one of four templates in the library (task, planner, supervisor, tool). Use PB-AGT-01-01 to choose the right template and runtime before you start here.
1. Summary¶
This how-to shows how to build a production-grade single-task agent using the standardised LangGraph task agent template. It implements a typed state schema with explicit channel reducers, transient failure resilience using node-level RetryPolicy, and two-tier termination consisting of an in-state soft step budget and a hard recursion_limit. Applying this template ensures agents execute reliably, recover from transient errors, and terminate gracefully without runaway loops or uncontrolled API token spend.
Risk: Medium (missing stop rules cause loops and runaway spend). Typical duration: 1-2 days for the first agent.
Facts checked against LangChain and Microsoft documentation on 6 November 2026.
2. When to use and when not to¶
Use it when:
- Creating a new task-oriented agent or standardising an existing ad hoc agent script to enterprise production standards.
- Building an agent that performs iterative, multi-step tasks (such as document extraction, multi-source research, data transformation, or automated remediation).
- Integrating external model calls or APIs that require automatic retries on transient network and rate-limit errors without duplicating side effects.
- Persisting state across execution turns or worker restarts using a durable database checkpointer.
Do not use it when:
- The task is a single-turn prompt-response or simple deterministic pipeline without branching or retry requirements; use a fixed workflow instead; choose it with PB-AGT-03-01.
- You are designing multi-agent topologies (such as supervisor-worker or hierarchical peer teams); use PB-AGT-06-01.
- You are evaluating framework choices between LangGraph, Microsoft Agent Framework, or Microsoft Foundry Agent Service: use PB-AGT-01-01.
- The agent executes sensitive or irreversible actions (such as financial transactions or data deletion) requiring human sign-off: use PB-AGT-05-01.
- Diagnosing or stopping an agent actively stuck in a runaway loop in production: follow the emergency runbook PB-AGT-09-01.
- Running pre-production technical and operational sign-off: use the checklist in PB-AGT-01-05.
3. What you must know first¶
- State schema and reducers: LangGraph channels use typed definitions (such as Python
TypedDictor Pydantic); fields annotated withAnnotated[T, reducer_fn](e.g.operator.add) determine how node updates are merged rather than overwritten (LangGraph: Use the graph API). - Node idempotency and
RetryPolicy:RetryPolicyhandles transient exceptions (such as network drops or HTTP 5xx) with configurablemax_attemptsand exponential backoff; nodes with external side effects must use idempotency keys to prevent duplicate execution during retries (LangGraph: Use the graph API). - Two-tier termination boundaries: Always separate the soft termination budget from the hard safety stop. An in-state step budget (e.g.,
max_steps = 8) allows the agent router to exit gracefully toENDwith a user-friendly explanation, whereasrecursion_limitcaps the number of super-steps and raisesGraphRecursionErrorwhen hit (LangGraph: GRAPH_RECURSION_LIMIT error). The default changes between versions (10,007 super-steps from 1.1.4), so always setrecursion_limitexplicitly in the run config and never rely on the default (LangGraph: Graph API overview). - Durable thread checkpoints: LangGraph scopes conversation and agent memory by
thread_idin{"configurable": {"thread_id": "..."}}. For production multi-process environments, initialise tables once viacheckpointer.setup()(LangGraph: Checkpointers). UseInMemorySaveronly in local development; it loses all state on process restart (LangGraph: Persistence). - State trimming: Avoid unbounded list growth in state channels with append reducers; prune or summarise conversation history in a dedicated node to prevent model context window exhaustion.
- Human approval needs a durable checkpointer. If this agent will later need a human to approve an action, the interrupt pattern requires a persistent checkpointer.
PostgresSaveris the Orion default for LangGraph (see PB-AGT-01-01). See PB-AGT-05-01 and PB-AGT-04-02. (LangGraph: Interrupts) - Foundry hosted agent context. This template is designed to run as a Foundry hosted agent (the default) or on Azure Container Apps/AKS. The Foundry Agent Service deletes hosted sessions after 30 days of inactivity; keep durable state in the checkpointer, not in the session. Check current limits before deployment. (Hosted agents in Foundry Agent Service)
4. Prerequisites and access¶
- Python 3.11 or later, with
langgraph,langgraph-checkpoint-postgresandpsycopg[binary,pool]installed from the template lock file. - Sandbox Azure Database for PostgreSQL Flexible Server for the checkpointer (Amazon RDS or local PostgreSQL on the AWS/OSS path).
- Model deployment in a sandbox Foundry project (Amazon Bedrock on the AWS path), with managed identity or API key credentials.
- Task-agent template repository cloned.
- Decision record from PB-AGT-01-01 with template: task-agent, chosen: langgraph and hosting set.
5. Steps¶
Follow these steps to build and verify the task agent:
-
Define typed state schema with explicit reducers and step budgets. Copy the task-agent template and rename the state schema fields for your domain. Annotate cumulative history fields with
operator.add, but keep status flags, step counters, and outputs as overwrite channels. Check: Verify that updating the state updates scalar fields and appends to annotated list fields as expected. -
Categorise nodes and isolate side effects with idempotency keys. Separate read-only reasoning nodes from nodes that execute external side effects (such as sending emails, writing database rows, or charging cards). !!! warning "Never retry non-idempotent side effects" Applying a
RetryPolicyto an external API call without an idempotency key can cause duplicate payments, multiple tickets, or duplicated emails if network acknowledgments drop. Always supply a deterministic idempotency key${THREAD_ID}:${STEP_NUMBER}to external endpoints. Check: Inspect node functions and verify all mutating calls accept an idempotency key derived from the thread configuration. -
Attach
RetryPolicyto transient network and model nodes. ApplyRetryPolicy()with the documented defaults (max_attempts=3,backoff_factor=2.0), or a value the team records as an Orion default, to nodes that invoke LLMs or external read-only APIs. Do not attach retries to internal deterministic validation nodes. Setretry_onexplicitly to the transient exception types your model or tool client raises; the default excludesOSErrorand its subclasses (such as built-inConnectionErrororTimeoutError). Check: Withretry_onset to includeConnectionError, simulate a mockConnectionErrorin a unit test and confirm the node retries up tomax_attemptsbefore raising the exception. -
Implement two-tier termination routing (soft budgets vs hard limit). Track
step_countandtokens_usedin state againstmax_stepsandmax_tokens. The routing function returnsENDwhendoneis true and routes to a finalise node when either budget is reached. The finalise node writes status and a clear final message to state, then goes toEND. Do not set state inside the routing function: LangGraph only applies updates returned by nodes (or aCommand), so values set in a router are lost. Check: Invoke the graph with an unsolvable goal and confirm that, for both the step budget and the token budget, the run reachesENDwithstatus: budget_exceededand the final message persisted, without raisingGraphRecursionError. -
Configure execution parameters and set
recursion_limit. Set the runtimerecursion_limitin the configuration dictionary above the soft step budget (for example,max_steps = 8,recursion_limit = 25) to provide a hard circuit breaker against runaway execution. Check: Confirm the graph executes normally when within budget and halts ifrecursion_limitis intentionally set lower than the steps required. -
Initialise the persistent checkpointer schema. Open the checkpointer with
with PostgresSaver.from_conn_string("${POSTGRES_CONN_STRING}") as checkpointer:and callcheckpointer.setup()once in the sandbox (and in the deployment migration job for later environments). Check: Connect to PostgreSQL withpsqland confirm tablescheckpoints,checkpoint_blobs, andcheckpoint_writesexist. -
Compile the graph with checkpointer and test execution. Compile the
StateGraphwith the initialised checkpointer and run an end-to-end task using a uniquethread_id. Check: Inspect state snapshot usinggraph.get_state(config)and verify state fields and execution steps are persisted. -
Execute test cases for retry recovery, budget termination, and crash recovery. Run unit tests covering standard completion, transient failure recovery, soft-budget termination, and process restart recovery. Check: Confirm all test cases pass and emit structured trace logs with matching
thread_id. -
Check the stopped-run trace. Open the trace for the budget-stop test run from step 8. Check: The last span shows
status: budget_exceededand the final message text.
Draft snippet: LangGraph task agent with RetryPolicy and two-tier termination¶
import operator
from typing import Annotated, Literal, TypedDict
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import RetryPolicy
# 1. State schema with explicit reducer and step budget
class TaskState(TypedDict):
task: str
steps: Annotated[list[str], operator.add]
step_count: int
max_steps: int
done: bool
status: str
final_output: str
# 2. Worker node with transient error retry
def execute_step(state: TaskState) -> dict:
current_count = state.get("step_count", 0) + 1
task_desc = state["task"]
# Simulated worker logic: marks done when step 3 is reached
is_done = current_count >= 3
output_message = f"Completed step {current_count} for '{task_desc}'"
return {
"steps": [output_message],
"step_count": current_count,
"done": is_done,
"final_output": "Task successfully completed." if is_done else "",
}
# 3. Router implementing soft termination budget
def route_next(state: TaskState) -> Literal["execute_step", "__end__"]:
if state["done"]:
return END
# Soft budget check: terminates gracefully before hitting recursion_limit
if state["step_count"] >= state["max_steps"]:
state["status"] = "budget_exceeded"
state["final_output"] = (
f"Terminated gracefully after reaching maximum step budget of {state['max_steps']}."
)
return END
return "execute_step"
# 4. Graph assembly
builder = StateGraph(TaskState)
# Node configured with exponential backoff retry policy
builder.add_node(
"execute_step",
execute_step,
retry_policy=RetryPolicy(
max_attempts=3,
initial_interval=0.5,
backoff_factor=2.0,
jitter=True,
),
)
builder.add_edge(START, "execute_step")
builder.add_conditional_edges("execute_step", route_next, ["execute_step", END])
# 5. Compilation with checkpointer
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)
# 6. Invocation with hard recursion_limit safety net
config = {
"configurable": {"thread_id": "task-thread-42"},
"recursion_limit": 25, # Hard ceiling well above soft max_steps (8)
}
initial_state: TaskState = {
"task": "Extract customer sentiment from feedback logs",
"steps": [],
"step_count": 0,
"max_steps": 8,
"done": False,
"status": "in_progress",
"final_output": "",
}
result = graph.invoke(initial_state, config)
print("Final status:", result.get("status"))
print("Step count:", result.get("step_count"))
print("Final output:", result.get("final_output"))
Snippets to add:
snippets/templates/task_agent/graph.py: Complete task agent template with PostgreSQL checkpointer and Pydantic validation (python)snippets/tests/test_task_agent.py: Pytest suite exercising standard completion, transient retry, and budget stop (python)snippets/config/agent.yaml: Configuration file for timeout, step budgets, and retry limits (yaml)
6. Verify it works¶
To verify the task agent template implementation, execute the test suite in the sandbox:
- Happy Path Test:
Invoke the graph with
max_steps: 8. Confirm the agent iterates untildone == True, completes in 3 steps, and setsfinal_outputcleanly. - Transient Failure Retry Test:
Simulate a transient error that the node's
retry_onmatches (for exampleConnectionError) on attempt 1 inexecute_step. ConfirmRetryPolicycatches the error, retries automatically, and succeeds on attempt 2 without failing the thread. - Soft Budget Termination Test:
Set
max_steps: 2with an objective requiring 5 steps. Confirm execution routes toENDcleanly, marksstatus: budget_exceeded, and does not throwGraphRecursionError. - Hard Recursion Safety Test:
Set
recursion_limit: 3andmax_steps: 10. Confirm that if a soft stop condition is omitted, LangGraph halts execution and raisesGraphRecursionErrorat step 3. - Checkpoint Persistence Test:
Invoke the graph with a thread ID. Query
graph.get_state(config)and confirm that the persisted state contains the full history of executed steps and counters.
7. Known issues and fixes¶
| Symptom | Likely cause | Fix |
|---|---|---|
GraphRecursionError: Recursion limit of 25 reached |
Execution cycle without a reachable stop condition | Add an explicit soft step budget check (step_count >= max_steps) in conditional routing edges and test it. Raise the limit only for genuinely long graphs. |
| Duplicate side effects after transient error recovery | Mutating action executed inside a retried node without idempotency | Move side effects to dedicated downstream nodes and supply idempotency key ${THREAD_ID}:${STEP_NUMBER} |
| State grows continuously and model calls become slow | Channels with operator.add accumulate conversation history without pruning |
Implement a dedicated pruning or summarisation node to trim state history |
ProgrammingError or missing tables with PostgreSQL checkpointer |
checkpointer.setup() was not run before live graph invocations |
Execute checkpointer.setup() during application deployment or migration setup |
Transient connection errors or timeouts are not retried, or a custom retry_on retries client errors |
default_retry_on excludes OSError and its subclasses (including built-in ConnectionError and TimeoutError); an over-broad custom retry_on can retry 4xx or validation errors |
Set retry_on explicitly to the transient exception types your client raises; keep 4xx and validation errors non-retryable |
8. Monitoring and alerts¶
Thresholds are Orion team defaults, not platform recommendations. Tune them against a measured baseline.
| Signal | Alert when | Action |
|---|---|---|
GraphRecursionError exceptions in application logs |
Greater than 0 in any window | Trigger emergency triage via PB-AGT-09-01 and inspect edge termination logic |
| Node retry rate per thread | Greater than 3x the 7-day median | Check upstream LLM endpoint availability, network stability, rate-limit quotas, and the node's retry_on settings |
Runs terminating due to soft budget exhaustion (budget_exceeded) |
Above the baseline rate measured in the sandbox dry-run (threshold TO VERIFY before go-live) | Review prompt complexity, increase step budget if justified, or refine planner decomposition |
| Checkpoint write latency | Above the p95 baseline measured in the sandbox (threshold TO VERIFY) | Check PostgreSQL database CPU, connection pool sizing, and table index health |
| Production agents not built from the task-agent template | Any new one in the monthly inventory | Agent lead reviews and either adopts the template or records an exception in the decision record |
9. AWS and open-source equivalents¶
- AWS: The same LangGraph code runs on AgentCore Runtime or any container host. Amazon Bedrock AgentCore Runtime is a serverless runtime for deploying and scaling agents; it fills the same role as Foundry hosted agents. (What is Amazon Bedrock AgentCore?)
- OpenAI Agents SDK: Run-level turn limit (
max_turns, raisesMaxTurnsExceeded), guardrails and tool error handling; check current names in its docs. Foundry hosted agents and AgentCore both run agents built with it. (OpenAI Agents SDK: Running agents, OpenAI Agents SDK: Guardrails) - Amazon Bedrock Agents Classic: New sign-ups are closed. Do not use it for new work.
10. Validation record¶
Pending. Filled in by the dry-runner (see docs/validation-protocol.md).
Sandbox scenario: A second engineer builds a new task agent from the playbook in a sandbox, forces a tool failure and a looping condition, and passes when the retry recovers, the budget stops the loop before recursion_limit and all tests are green.
Pass criteria: The retry recovers, the budget stops the loop before recursion_limit and all tests are green.
Record¶
| Item | Detail |
|---|---|
| Playbook | PB-AGT-01-02 at commit [hash] |
| Dry-run by | [Name] (must not be author or reviewer) |
| Date | YYYY-MM-DD |
| Sandbox | [Resource group or tag, tier and region] |
| Scenario | [Scenario followed] |
| Time taken | [Hands-on time] hands-on, [waiting time] waiting |
| Result | [PASS / PASS WITH NOTES / FAIL] |
| Notes | 1. [Step number: defect or explanation; blocking/non-blocking; fixed/logged] |
| Signed off by | [Reviewer name and date] |
11. Sources¶
Checked on 6 November 2026.
- LangChain, LangGraph: Use the graph API (RetryPolicy, Send, Command): state reducers, RetryPolicy, Send, Command.
- LangChain, LangGraph: Fault tolerance: RetryPolicy, default_retry_on and exception filtering.
- LangChain, LangGraph: GRAPH_RECURSION_LIMIT error: GraphRecursionError and how to raise the limit.
- LangChain, LangGraph: Graph API overview: recursion_limit in run config, default by version, graph compilation.
- LangChain, LangGraph: Checkpointers: checkpointer setup, thread_id scoping, PostgresSaver.
- LangChain, LangGraph: Persistence: InMemorySaver loss on restart, PostgresSaver, Store for long-term memory.
- LangChain, LangGraph: Interrupts: interrupt() and Command(resume=...) for human approval, durable checkpointer requirement.
- LangChain, LangGraph: Workflows and agents: routing and evaluator-optimizer patterns.
- Microsoft Learn, What is Microsoft Foundry Agent Service?: agent types, hosted agent definition.
- Microsoft Learn, Hosted agents in Foundry Agent Service: hosted agent architecture and 30-day session deletion.
- Microsoft Learn, Foundry Agent Service limits, quotas and regional support: 128 tools per agent, concurrent session quota.
- AWS, What is Amazon Bedrock AgentCore?: AgentCore Runtime as a Foundry hosted-agent equivalent on AWS.
- OpenAI, OpenAI Agents SDK: Running agents: run-level turn limits, max_turns, MaxTurnsExceeded.
- OpenAI, OpenAI Agents SDK: Guardrails: input and output guardrails.
Related playbooks¶
- PB-AGT-01-01: Choose the agent template and runtime: choose template and runtime before building.
- PB-AGT-01-04: Upgrade the agent framework or model version without regressions: upgrades after the agent is live.
- PB-AGT-01-05: Agent go-live checklist: pre-production sign-off checklist.
- PB-AGT-02-01: Define a tool with Pydantic schemas, an error contract and a registry entry: builds tools attached to this agent.
- PB-AGT-03-01: Choose a planner pattern: workflow, ReAct, plan-and-execute, reflexion or orchestrator-worker: choose a fixed workflow or planner pattern before building.
- PB-AGT-04-01: Choose memory layers and stores: memory choices after the runtime is chosen.
- PB-AGT-04-02: Set up LangGraph checkpointer and store on Azure: production PostgresSaver setup.
- PB-AGT-04-04: Run memory retention, growth control and state schema migration: plans checkpoint storage retention.
- PB-AGT-05-01: Build approve, edit, reject and timeout paths with LangGraph interrupts: adds human approval to this template.
- PB-AGT-06-01: Choose a multi-agent topology: sequential, parallel, supervisor, handoff or debate: choose multi-agent topologies for supervisor-worker or peer teams.
- PB-AGT-07-01: Score agent trajectories: release-gate evaluation.
- PB-AGT-09-01: Stop and triage an agent stuck in a loop or burning tokens: emergency runbook when stop rules fail.
- PB-AGT-10-01: Build an MCP server from the Orion Python template: builds MCP tool servers exposed to this agent.