"The agent is the pattern"
Every agent — regardless of how you build it — runs the same fundamental loop:
Receive goal
→ Claude thinks, picks a tool
→ Execute tool
→ Feed result back to Claude
→ Claude thinks again
→ Repeat until goal is metThat loop is the pattern. It never changes. Whether you're building a support agent, a coding agent, or a research agent — the loop is identical.
Workflow — deterministic
You know every step upfront and encode them in code. The sequence never changes regardless of input. Think of it like a Java method with a fixed execution path — same steps, same order, every time. The value is predictability, auditability, and guardrails at each step.
Step 1 → Step 2 → Step 3 → Done
(always, no deviation)Agent — non-deterministic
You give Claude a goal and a toolset, and it decides at runtime which tools to call, in what order, and how many times. The path through the work is chosen by the model, not by your code. The value is flexibility — it handles inputs and situations you didn't anticipate when you wrote the system.
Goal → Claude decides → Tool A → Claude decides → Tool C → Tool A again → Done
(path chosen at runtime, different every time)The one practical dimension worth adding
Most real production systems are neither pure workflow nor pure agent — they're a hybrid. The outer shell is a deterministic workflow (step 1: classify intent, step 2: route to handler, step 3: format output), and specific steps inside delegate to an agent where flexibility is genuinely needed.
Reach for the agent only at the points where the path truly cannot be predetermined. Keep everything else deterministic — it's cheaper, safer, and easier to debug.
In your raw loop (Messages API):
You pass everything on every single API call:
// You rebuild and resend this on EVERY turn of the loop
ObjectNode requestBody = mapper.createObjectNode();
requestBody.put("model", "claude-sonnet-4-6"); // sent every turn
requestBody.put("max_tokens", 1024); // sent every turn
requestBody.set("tools", tools); // sent every turn
requestBody.set("messages", messages); // sent every turnEvery iteration of your loop reconstructs and resends the full request. You own that loop in your Java code.
In Managed Agents:
You define all of those once upfront when creating the agent, and Anthropic stores it:
// One-time agent definition — stored by Anthropic
{
"model": "claude-sonnet-4-6", // same as your requestBody.put("model"...)
"system_prompt": "You are a support...", // same as your system prompt message
"tools": [...], // same as your requestBody.set("tools"...)
"mcp_servers": [...], // MCP servers to connect (new — not in raw loop)
"skills": [...] // pre-built capabilities (new — not in raw loop)
}Anthropic gives you back an agent ID. From that point, every request is just:
// Every subsequent call — just the user input
{
"agent_id": "agt_01ABC",
"input": "What is the weather in Mumbai?"
}No model, no tools, no system prompt repeated — Anthropic already has all that stored against the agent ID.
The two new parameters you don't have in your raw loop:
mcp_servers — instead of manually calling ListToolsRequest and wiring MCP yourself, you just declare which MCP servers the agent should connect to. Anthropic handles the connection and tool registration automatically.
skills — pre-built capabilities Anthropic provides (like web search, code execution) that you can attach to the agent without writing tool implementations yourself. There's no equivalent in your raw loop — you'd have to implement those tools manually.
Typical production pattern — hybrid usage
// Simple single-turn classification → raw Messages API
// No loop needed, full control, minimal overhead
ObjectNode requestBody = mapper.createObjectNode();
requestBody.put("model", "claude-sonnet-4-6");
requestBody.set("tools", tools);
requestBody.set("messages", messages);
JsonNode response = callClaudeAPI(requestBody);
// Complex multi-step agentic task → Agent SDK
// Loop management, tool execution handled automatically
AgentResult result = sdk.run(goal, tools, systemPrompt);Why enterprises still lean toward raw Messages API even for agentic work
This is the part that surprises most people. The reasons are practical:
Observability — In enterprise environments, every tool call, every context state, every iteration needs to be logged and auditable. The raw loop makes every step explicit in your own code. SDK abstracts those steps — which means your existing monitoring, logging, and alerting infrastructure has less visibility into what's happening inside the loop.
Existing infrastructure — Teams like yours already have Spring Boot, WebFlux, Jackson, retry logic, circuit breakers. The SDK introduces a new dependency with its own design decisions that may conflict with or duplicate what you already have.
Control over context management — As you learned earlier, context management is critical in production. The raw loop lets you apply pruning, compaction, and token counting exactly where you need them. The SDK makes those decisions for you — which is convenient but sometimes not what you want.
Debugging — When something breaks in production at turn 6 of a complex agentic loop, you want to see exactly what was sent and received at each step. Raw loop gives you that naturally. SDK requires you to dig into its internals.
Human-in-the-loop (HITL): Insertion points and when each applies
A human-in-the-loop checkpoint pauses agent execution and routes to a human review step before proceeding. The question that determines where to insert one is: what is the worst possible outcome if this step runs without a human check?
| Insertion point | What triggers the check | Risk level it addresses |
|---|---|---|
| Before a destructive tool call | The agent is about to execute a write, delete, or send operation. | High: irreversible actions where a wrong call cannot be undone |
| After a planning step | The agent has generated a plan and is about to begin executing it. | Medium: incorrect plans that would produce the wrong outcome even if all steps execute correctly |
| On unexpected output | The tool result contains an error flag, an empty result, or a value outside expected bounds. | Variable: catches failure modes that retry logic alone will not resolve |
No comments:
Post a Comment