Chapter 21
21 / 50

Advanced Agent Topics

⏱️ 45 min

An advanced agent is not simply a collection of models. The useful upgrade is to leave uncertain judgement to the model while code owns permissions, state, budgets, and acceptance. When the task path is known in advance, a deterministic workflow is usually cheaper, faster, and easier to debug.


Decide whether the task needs an agent

Task characteristicRecommended structureReason
Fixed steps, clear rules, predictable failuresCode or deterministic workflowMaximum control; the model handles only ambiguous nodes
Inputs fall into stable categoriesRouter plus specialist flowsEach class gets its own tools, prompt, and evals
Subtasks are independentParallel or MapReduceReduce total latency and aggregate once
Output needs repeated generation and reviewEvaluator–OptimizerA rubric drives bounded iteration
The path cannot be enumerated before executionAgent loopThe model chooses tools from environmental feedback

Anthropic distinguishes workflows, where code defines the path, from agents, where the model directs the process. Start with the simplest structure and add autonomy only when real tasks show fixed paths are insufficient.

ordinary function → one LLM call → workflow → tool-using agent → manager + specialists

Every step to the right adds capability, cost, latency, and failure surface.

Seven orchestration patterns

PatternData pathGood fitMain risk
Prompt ChainingA → B → CDocument pipelines and staged reviewUpstream errors propagate
RoutingClassify → specialist branchSupport, tickets, and content triageSilent failure after misrouting
ParallelizationA, B, and C run togetherMulti-source research and independent checksConflicting or duplicated results
Orchestrator–WorkersManager → workers → synthesisDynamic numbers of subtasksManager over-decomposes work
Evaluator–OptimizerGenerate → score → reviseDeliverables with a clear rubricUnbounded optimisation loops
HandoffCurrent agent → specialistConversational specialist ownershipLost context and responsibility
Autonomous LoopObserve → decide → act → observeOpen environments and unknown pathsUncontrolled cost and side effects

Do not make multi-agent architecture the default. If one agent can use a small, well-defined tool set, splitting the job into five roles creates more handoffs, duplicated context, and ambiguous failures.


Model messages are not the run database

Persist an explicit production run:

type AgentRun = {
	runId: string;
	taskId: string;
	status: 'queued' | 'running' | 'waiting_approval' | 'verified' | 'failed';
	objective: string;
	step: number;
	maxSteps: number;
	budget: { maxTokens: number; maxCostUsd: number; deadlineMs: number };
	observations: ObservationRef[];
	pendingAction?: ProposedAction;
	idempotencyKeys: string[];
	finalEvidence?: EvidenceRef[];
};

Store references and compact summaries in observations, not copies of every raw response. Large artefacts belong in object storage or the source system and should be retrieved by stable ID.


A minimal controlled loop

async function runAgent(run: AgentRun) {
	while (run.status === 'running') {
		assertWithinBudget(run);

		const decision = await decideNextAction({
			objective: run.objective,
			recentObservations: await loadWorkingSet(run),
			availableTools: await toolsForTask(run.taskId)
		});

		if (decision.type === 'finish') {
			const evidence = await verifyOutcome(decision.claims);
			run.status = evidence.every(item => item.valid) ? 'verified' : 'failed';
			run.finalEvidence = evidence;
			break;
		}

		validateToolCall(decision.tool, decision.arguments);

		if (requiresApproval(decision)) {
			run.status = 'waiting_approval';
			run.pendingAction = decision;
			break;
		}

		const result = await executeInBoundary(decision, {
			timeoutMs: 20_000,
			idempotencyKey: `${run.runId}:${run.step}`
		});

		await appendObservation(run, summarizeToolResult(result));
		run.step += 1;
	}
}

The important part is not the while loop. Every iteration checks budget, arguments, policy, idempotency, and the finish claim.


Tool contracts define the agent's ceiling

const sendRefund = {
	name: 'send_refund',
	description:
		'Refund one captured payment after approval. Do not use for pending payments or goodwill credit.',
	inputSchema: {
		type: 'object',
		properties: {
			paymentId: { type: 'string' },
			amountCents: { type: 'integer', minimum: 1 },
			approvalId: { type: 'string' },
			reasonCode: { type: 'string', enum: ['duplicate', 'service_failure'] }
		},
		required: ['paymentId', 'amountCents', 'approvalId', 'reasonCode']
	}
};

A mutation should return the provider request ID, whether it was already applied, rollback availability, and an evidence URL. success: true is not enough for recovery or audit.


Permissions and human approval

RiskExampleDefault policy
Read-onlySearch documents, read an orderAutomatic within a scoped dataset
Reversible writeCreate a draft, add a tagAutomatic or sampled review with undo
External communicationSend email or chatShow recipient and content before approval
Money or productionRefund, deploy, deleteMandatory approval, idempotency, provider read-back
Irreversible or high-impactBulk deletion, public publicationTwo-person approval or prohibit direct execution

The approval view should show target, arguments, expected impact, source evidence, and rollback plan. “The agent wants to call a tool” is not enough information.


Stop loops before they become incidents

Define maximum steps, tokens, cost, and wall-clock time. Stop repeated calls with identical arguments, consecutive identical errors, exploration after the goal is met, and runs waiting for missing approval or credentials.

function detectLoop(history: ToolCall[]) {
	const recent = history.slice(-3).map(call => JSON.stringify([call.name, call.arguments]));
	return recent.length === 3 && new Set(recent).size === 1;
}

Return a diagnostic state such as blocked: missing_approval, not a generic failure.


Manager and handoff models

A manager owns the final response and invokes specialists as tools. Use it for one voice, one safety policy, and cross-domain synthesis. A handoff transfers the conversation to a specialist who needs to ask follow-up questions and own the user interaction.

{
	"objective": "Resolve duplicate charge",
	"facts": ["payment_1 captured", "payment_2 captured"],
	"evidenceIds": ["trace_87", "invoice_42"],
	"completed": ["identity verified"],
	"openDecision": "refund which payment",
	"prohibitedActions": ["send refund without approval"]
}

Do not forward the entire conversation by default. The specialist needs the smallest sufficient context and a clear responsibility boundary.


Tracing and evaluation

Trace run ID, model and configuration versions, tool arguments, latency, errors, tokens, cost, approvals, and provider receipts.

MetricQuestion
Task successDid the user outcome happen?
Tool selectionWas the correct tool selected?
Argument validityDid arguments satisfy schema and business rules?
Side-effect precisionWere unnecessary writes avoided?
Recovery rateCan the run safely resume after timeout or crash?
Cost and latencyWhat does one successful resolution consume?

A fluent answer paired with two refunds is still a severe failure.


Common mistakes

MistakeConsequenceFix
Sending every task to an autonomous agentLarger latency and failure surfaceMove predictable tasks back to workflows
Overlapping tool responsibilitiesUnstable tool choiceRewrite boundaries and narrow the active tool set
Letting the model declare successPremature completionVerify through code or provider read-back
Retrying writes without idempotencyDuplicate payments, messages, or publicationsStable task ID plus provider request ID
Sharing full history with every specialistContext growth and instruction conflictsStructured handoff payloads

Practice: duplicate-charge investigation

  1. Mark deterministic steps and decisions that need a model.
  2. Define boundaries for order, payment, identity, and refund tools.
  3. Add an eight-step limit, 30-second tool timeout, and loop detection.
  4. Produce an approval diff before refunding.
  5. Crash after approval and verify restart does not refund twice.
  6. Mark success only after reading the provider state back.

Definition of done

  • I can explain why the task needs an agent instead of a workflow.
  • Every mutation has an approval or explicit automation policy.
  • The loop has step, cost, time, and repetition limits.
  • Run state survives process restart.
  • Completion is verified by external evidence.

Official references

📚 Related resources

Common questions

Open a question to review the practical answer.

How do I pick between ReAct, Plan-and-execute, and Tree of Thoughts agent styles?

Match style to task shape. ReAct (reasoning + tool use interleaved) fits search/code tasks where you read tool results and decide the next step. Plan-and-execute (plan first, then execute) suits longer tasks — committing to a plan upfront stays more stable than re-deciding every ReAct step. Tree of Thoughts / Reflexion fits tasks needing multi-option exploration + self-critique + best-pick (puzzles, complex math). Router agents dispatch requests by intent/domain to specialized sub-agents, saving tokens.

How do I stop an Agent from looping or calling tools forever?

Through stop conditions — at least three caps: max steps (e.g., 10), max tool calls (e.g., 20), max time (e.g., 60s); any trigger breaks. Loop detection can hash the last N (tool, args) pairs and abort on repeats. The chapter pseudocode is the standard pattern: `while not done: ... if stop_condition(): break`. In production, also add: distinct exit codes for completion vs timeout vs kill (upstream needs to know), cancel-rate monitoring, and routing loop cases to a trace store for post-mortem.

What's dual-model verification, and why use cheap-model + expensive-model review?

Dual-model verification: a cheap model (e.g., GPT-4o-mini) runs the agent's main loop and tool calls; an expensive model (e.g., Claude Sonnet 4.5 / GPT-5) only reviews at the end — checks whether the answer cites tool outputs, follows instructions, and contains no hallucinations. The rationale: 80% of steps don't need top-tier reasoning, so the most expensive compute lands at the highest-ROI "quality gate." Combined with self-critique (agent verifies its own answer vs instructions/citations), this lifts production quality measurably while adding only 10-20% cost.

How do I add human approval for Agent's high-risk actions (sending emails, prod changes)?

Three layers: (1) tool-level allowlist for external domains/APIs, blockwrites by default unless explicitly permitted; (2) guardrails run PII / dangerous-action checks before and after tool calls; (3) human approval steps gate risky actions (emails, transactions, prod changes) behind a node that requires user confirmation. Frameworks supporting human-in-the-loop (like LangGraph) can pause at an approval node and wait for the user. OpenAI Agent Mode follows the same logic — deliver to the final channel and wait for confirmation before sensitive/irreversible steps.

What fields must Agent observability logs at least capture?

Per-run trace must capture: step index, chosen tool, inputs/outputs, duration, errors (code + message), tokens consumed. At the metrics layer, at least four: success rate, avg steps, tool error rate, cancel rate. Replay is critical — store deterministic inputs (same input reproduces) plus same seed/config for offline reproduction. Running a production Agent without traces is flying blind — when bugs hit, you can't tell whether the plan was wrong, the tool died, or the model hallucinated.