English · 中文
Position: The third layer of the four-layer progression of LLM application engineering (between context engineering and harness engineering) Research date: 2026-05-01
An agent is not a fancier LLM call. It is the quartet “LLM + tools + memory + loop” — given a goal, it plans on its own, takes action, observes the result, and adjusts its strategy until the task is done or it is explicitly stopped. From an engineering standpoint, an agent is the unit of abstraction that cuts vertically through the prompt, context, and harness layers: its thinking is given templates by prompt engineering (ReAct and others), its field of view is supplied by context engineering (RAG, memory, tool schemas), and its runtime is built by harness engineering (sandbox, gateway, guardrails, observability).
In 2026, agents have split into three clear tracks:
Mainstream frameworks have converged from a wide proliferation to a few that dominate: LangGraph (the production default) / CrewAI (the fastest to pick up) / OpenAI Agents SDK (tied to the OpenAI ecosystem) / Smolagents (minimal) / Mastra (TypeScript-native). Microsoft has moved AutoGen into maintenance, and Microsoft Agent Framework has taken over.
On the latest rankings as of April 2026 for the mainstream benchmarks (SWE-bench Verified / GAIA / WebArena / OSWorld), Anthropic models nearly monopolize the top. Claude Opus 4.7 is reported to land in the ~87% range on SWE-bench Verified (for the exact score and the live board, see Anthropic’s official release page and the swebench.com leaderboard, as of this survey’s date 2026-05-01; scores change with version and with evaluation criteria). Berkeley RDI also warns: a set of mainstream agent benchmarks (including SWE-bench / WebArena / GAIA / Terminal-Bench and others) have been shown to be hackable to near-perfect scores without the task actually being completed — a score has to be read against the specific evaluation methodology.
Failure modes and real incidents are what most deserve caution: 90% of production agents die in a Loop of Death (an infinite loop); a single Claude Code sub-agent burned 27 M tokens and ran for 4.6 hours; Amazon Kiro AI autonomously deleted a production AWS environment and caused a 13-hour outage; Replit AI deleted a production database during a code freeze; Google AI wiped an entire disk while clearing a cache. There is one lesson: without a max iteration cap, without an execution timeout, without a sandbox, and without a deny-first permission model, an agent in production is a bomb.
To understand what an agent is, first rule out three common confusions:
Anthropic’s engineering blog gives a widely cited definition: “An agent is the pattern of an LLM using tools in a loop” — “loop” and “autonomy” are the two core features.
A minimal agent has to have four things:
| Element | Meaning | If missing, it becomes |
|---|---|---|
| Goal | A goal | No goal = chatbot |
| Tools | Callable capabilities (API / tool / compute / retrieval) | No tools = pure generation |
| Loop | A think-act-observe loop | No loop = a single call |
| Memory | State kept across iterations of the loop | No memory = an amnesiac agent |
An agent’s “degree of autonomy” is a continuous spectrum, not a binary variable:
Human-in-the-Loop Human-on-the-Loop Fully Autonomous
───────────────── ───────────────── ─────────────────
每步行动都需用户确认 人在旁监督,关键步骤介入 完成才汇报
适合:高风险/医疗/金融 适合:中等风险/编码 适合:低风险/数据处理
e.g. 早期 Claude Code e.g. Cursor agent mode e.g. Devin / Manus
The 2026 industry consensus is this: the industry is not moving toward fully unsupervised autonomy; it is moving toward selective autonomy. Different actions inside one product should have different autonomy levels — reading a file can be fully autonomous; deleting a file must be HITL.
| Year | Paradigm | Paper / source | Core |
|---|---|---|---|
| 2022 | ReAct | Yao et al. | The minimal loop that interleaves Thought → Action → Observation |
| 2023 | Plan-and-Execute | LangChain | Plan the whole thing first, then execute, avoiding ReAct’s short-sightedness |
| 2023 | Reflexion | Shinn et al. | After a failure, write a “reflection note” and attach it to the next prompt, so the agent learns from experience |
| 2023 | Tree-of-Thoughts as agent | Yao et al. | Extend ToT reasoning into search at the agent level |
| 2024 | Constitutional Agent | Anthropic | Constrain agent behavior with a constitution / principles |
| 2024 | Multi-Agent System | AutoGen / CrewAI | Role division and negotiation among multiple agents |
| 2024-Q4 | Computer Use Agent | Anthropic | The agent operates a GUI directly (screenshot + mouse-and-keyboard tool) |
| 2025 | Agentic RAG | LangGraph | Delegate the RAG decision to the agent, which decides on its own when to retrieve and whether to retrieve again |
| 2026-Q2 | Background Computer Use | OpenAI Codex (2026-04) | The agent runs in its own desktop session, with multiple agents in parallel, without disturbing the main workflow |
ReAct is still the “base grammar” of almost every modern agent. The other paradigms add planning, reflection, search, multiple agents, and cross-modality on top of ReAct.
There is no shared industry consensus on a “standard N-layer model of agent architecture” — Lindy, Redis, arXiv, and others each split it differently (some use 5 layers of perception-cognition-action, some a single ReAct loop, and so on). The 4 layers below are the author’s simplified checklist, synthesized from several of those splits. They are not a canonical taxonomy. They are only a way to inventory the engineering components:
┌─────────────────────────────────────────────┐
│ Reasoning Layer(推理层) │
│ LLM 解读输入、规划行动序列、决定下一步 │
└────────────────────┬────────────────────────┘
│
┌────────────────────▼────────────────────────┐
│ Orchestration Layer(编排层) │
│ 控制流、任务序列、重试、错误处理、状态机 │
│ e.g. LangGraph state graph │
└────────────────────┬────────────────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌──────────────┐ ┌────────────────┐
│ Memory & Data│ │ Tool Integration│
│ 短期对话 │ │ API / Function │
│ 长期记忆 │ │ Calling / MCP │
│ RAG │ │ Sandbox 执行 │
└──────────────┘ └────────────────┘
┌─→ Goal ──→ Perception ──→ Reasoning ──→ Planning
│ │
│ ▼
│ Action
│ │
└─── Memory Update ←── Observation ←──────────┘
The loop continues until: (a) the goal is reached; (b) a stop condition fires (max iterations, timeout, user interrupt); (c) the agent itself judges “I cannot finish this; a human needs to step in.”
See chapter 02 (context engineering) for the RAG and memory sections, and chapter 04 (harness engineering) for tool integration and orchestration.
Building Effective AI Agents, published by Anthropic in December 2024, is one of the most cited essays on productionizing agents in 2025-2026. It splits “agent” systems into two kinds:
“Workflows are great when tasks are well-defined; agents shine when flexibility is required. Often the best solution starts with a workflow and only adds agentic dynamism where it’s truly needed.”
It also proposes 6 composable building blocks — the base primitives for building any agentic system.
The most atomic “agent”: an LLM plus three augmentations (retrieval / tools / memory). Every pattern above it is stacked on top of this one.
┌──────────┐
Input ───▶│ LLM │───▶ Output
│ + tools │
│ + memory │
│ + retrieval│
└──────────┘
Split the task into a sequence of steps. Each step’s LLM processes the previous step’s output. A gate (a program that decides whether to continue) can sit between steps.
Input → LLM 1 → [Gate]? → LLM 2 → [Gate]? → LLM 3 → Output
Typical setting: generate an outline → check that the outline matches the format → generate the body from the outline → check the length → translate.
Strength: each step can be optimized and tested on its own, and the process can be traced. Weakness: more steps means higher latency; information can be lost between steps. When to use: the task decomposes cleanly into linear steps, and each step has a clear quality gate.
One LLM looks at the input and decides which specialized path to take. Each path is optimized with its own prompt / tools / model.
┌─→ Specialist LLM A (用 prompt_A, tools_A)
Input → Router LLM┤
├─→ Specialist LLM B (用 prompt_B, tools_B)
└─→ Specialist LLM C (用 prompt_C, tools_C)
Typical setting: customer-support triage (a refund → a refund-specialist prompt; a technical issue → a technical prompt; chitchat → a general prompt); route by difficulty (a cheap model for an easy question, Opus for a hard one).
Strength: it makes “which prompt for which question” explicit, so each path can be optimized on purpose. Weakness: if the router is wrong, everything downstream is wrong. When to use: an input space with obvious categories.
Two variants:
map-reduce pattern) ┌─→ LLM 1 ─┐
Input ───────┼─→ LLM 2 ─┼──▶ Aggregator LLM ──▶ Output
└─→ LLM 3 ─┘
Typical settings: - Sectioning: split a long document into sections and summarize them in parallel; split a safety review across dimensions and check them in parallel (toxicity / PII / bias / injection) - Voting: multi-model voting on a code review; multi-prompt voting on content moderation, to cut false positives
Strength: latency is low and reliability is high (voting resists a single failure). Weakness: the cost is N times a single run. When to use: the subtasks are truly independent, or you need redundant reliability.
A central LLM dynamically decides the subtasks (they are not predefined), dispatches them to worker LLMs, and synthesizes the result at the end.
┌─→ Worker 1 (动态决定的子任务)
Input → Orchestrator┤
LLM (动态规划)├─→ Worker 2
└─→ Worker 3
↓
Synthesizer LLM ──▶ Output
The key difference from Parallelization: the subtasks cannot be defined in advance. The orchestrator sees the concrete input, and only then decides which subtasks to split it into.
Typical settings: - A code change across several files (only after reading the issue does the agent know which files to change) - A complex research task (it first has to decide which angles to look up before it can answer)
Strength: it handles complex tasks that are not known in advance. Weakness: cost and latency are high, and debugging is harder. When to use: a standard workflow cannot solve it, and the input is highly complex.
One LLM generates a response. Another LLM evaluates it and gives feedback. The first revises from that feedback. The loop continues until the response passes, or a cap is hit.
Input
│
▼
┌─→ Generator LLM ──▶ Response
│ │ │
│ │ ▼
│ │ Evaluator LLM (judge)
│ │ │
│ │ PASS ──→ Output
│ │ FAIL ──→ Feedback
└─────────┴────────────────┘
Typical settings: - Literary translation (the generator translates; the evaluator checks faithfulness, expressiveness, and elegance, and feeds back “too literal” and the like) - Complex search (the generator writes an answer; the evaluator checks coverage and feeds back “the X angle is still missing”) - Code generation (the generator writes code; the evaluator runs the tests and feeds back the failing cases)
Strength: a high ceiling on quality (iterative quality close to a human review). Weakness: the iteration count is not controllable, it can loop forever, and the cost is high. When to use: the evaluation criteria are clear, and the first draft is clearly worse than the result after iteration.
The previous five are workflows (the process is predefined). Agents are genuinely autonomous — inside the loop, the LLM decides for itself what to do next, which tool to use, and when to stop.
Input + Goal
│
▼
┌─→ LLM (think + act)
│ │
│ ▼
│ Tool / Action
│ │
│ ▼
│ Environment / Feedback
│ │
│ ▼
└─── Update Context
(until done or stopped)
When to upgrade to a real agent:
When not to use an agent (continue using workflow):
你的任务是?
│
├─ 单步即可解决 → 用 Augmented LLM
│
├─ 多步固定流程
│ ├─ 线性顺序 → Prompt Chaining
│ ├─ 按类型分流 → Routing
│ └─ 子任务独立 + 并行/冗余 → Parallelization
│
├─ 子任务无法预定义 → Orchestrator-Workers
│
├─ 需要质量迭代 + 评估清晰 → Evaluator-Optimizer
│
└─ 流程完全无法预定义 + 需要自主性 → Agent
Anthropic’s core advice: stay with a workflow (patterns 1-5) for as long as you can, and upgrade to an agent only when you truly need one. In production, 95% of “agentic systems” are served well enough by a workflow. Using an agent blindly brings in behavior that is hard to debug, unpredictable, and prone to runaway cost (see §9, failure modes).
| Type | Examples | Traits |
|---|---|---|
| Conversational Agent | ChatGPT / Claude.ai with tools | Conversation first; agent capability is supplementary |
| Task Agent | Zapier AI Actions / Make AI | After a trigger, it runs one process and stops |
| Coding Agent | Claude Code / Cursor / Devin | Long-running; edits files, runs tests, opens PRs |
| Browser Agent | Multi-On / Browser Use / Skyvern | Clicks, fills forms, and scrapes on web pages |
| Computer Use Agent | Anthropic Computer Use / OpenAI Operator | Operates mouse and keyboard across applications; reads the screen |
| Background Agent | OpenAI Codex Background Computer Use (2026-04) | Runs in its own session; multiple agents in parallel |
| Multi-Agent System | CrewAI / AutoGen applications | Several agents collaborate to finish a task |
The browser agent is the third-largest agent track of 2025-2026 (behind only coding and computer use). Benchmarks such as WebArena and WebVoyager have pushed this track to the SOTA.
| Framework | Language | Paradigm | Strength | ⭐ / status |
|---|---|---|---|---|
| Browser Use | Python | Fully autonomous AI (agent loop) | 91k ⭐, 89.1% success rate on WebVoyager | Current SOTA |
| Stagehand (Browserbase) | TypeScript | AI primitives on top of Playwright | 4 primitives: act / extract / observe / agent; readable and controllable | Rising strongly |
| Playwright | Multi-language | Deterministic automation | No AI; pure scripts; the fallback | Testing baseline |
| Skyvern | Python | LLM + computer vision | Visual understanding of pages it has not seen before | A startup product |
| Browserbase (SaaS) | API | Cloud stealth browser | Strongest against anti-bot defenses | The most commercially mature |
from browser_use import Agent
from langchain_anthropic import ChatAnthropic
agent = Agent(
task="在 Hacker News 上找今天 #1 的帖子,把标题和评论数告诉我",
llm=ChatAnthropic(model="claude-sonnet-4-6"),
)
result = await agent.run()
print(result)
# Browser Use 自动开 chromium、截图、决定点哪、抓 HTML、做循环直到任务完成
import { Stagehand } from "@browserbasehq/stagehand";
const stagehand = new Stagehand({ env: "LOCAL" });
await stagehand.page.goto("https://news.ycombinator.com");
// AI primitive: act(自然语言操作)
await stagehand.page.act("点击第一个帖子");
// AI primitive: extract(结构化抽数据)
const data = await stagehand.page.extract({
instruction: "提取帖子标题和评论数",
schema: z.object({ title: z.string(), comments: z.number() }),
});
// AI primitive: observe(让 AI 描述当前页面)
const observation = await stagehand.page.observe();
// AI primitive: agent(多步任务,自主完成)
await stagehand.page.agent({
task: "找到登录按钮,用 user@x.com 登录",
});
Stagehand’s design philosophy: the AI writes the selectors; the human writes the control flow. Brittle CSS selectors are replaced by natural-language act, while Playwright’s deterministic control of the flow stays. The industry consensus: Stagehand’s hybrid architecture will be the template.
你的任务是?
│
├─ 完全未知网站、AI 决定流程 → Browser Use(自主性最强)
├─ 流程基本已知、要稳定 → Stagehand(AI 局部 + 流程可控)
├─ 流程完全确定 → Playwright(不要用 AI)
├─ 视觉为主、HTML 不可靠 → Skyvern
└─ 需要反反爬云服务 → Browserbase(SaaS)+ Stagehand
In the second half of 2024, the OpenAI Realtime API opened the era of realtime voice agents. The 2026 mainstream:
| Approach | Mode | Latency | Fit |
|---|---|---|---|
| OpenAI Realtime API (GPT-4o / GPT-5) | Bidirectional audio streaming (WebSocket) | < 500ms | Customer support / personal assistant |
| Anthropic Voice (Claude) | Text + TTS (asynchronous) | 2-5s | Content-generation settings |
| Gemini Live API | Bidirectional multimodal (including video) | < 1s | Meeting assistant |
| Pipecat / LiveKit Agents | Open-source frameworks that orchestrate STT + LLM + TTS | 1-3s | Self-hosted; flexible |
| Vapi / Retell | SaaS; a hosted full stack for voice agents | < 1s | Startup MVP |
import asyncio, websockets, json, base64
async def voice_agent():
async with websockets.connect(
"wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2026",
extra_headers={"Authorization": f"Bearer {API_KEY}"},
) as ws:
# 配置 session(音色、tools、instructions)
await ws.send(json.dumps({
"type": "session.update",
"session": {
"voice": "alloy",
"instructions": "你是 ACME 客服。简短礼貌。",
"tools": [...], # 普通 function calling
"input_audio_transcription": {"model": "whisper-1"},
"turn_detection": {"type": "server_vad"}, # 服务器侧 VAD
},
}))
# 用户麦克风音频流(PCM16)持续 push 进去
async def push_mic():
while audio_chunk := mic.read():
await ws.send(json.dumps({
"type": "input_audio_buffer.append",
"audio": base64.b64encode(audio_chunk).decode(),
}))
# 模型音频回流,喂给扬声器
async def play_audio():
async for msg in ws:
event = json.loads(msg)
if event["type"] == "response.audio.delta":
speaker.play(base64.b64decode(event["delta"]))
await asyncio.gather(push_mic(), play_audio())
After the two-year Cambrian explosion of 2024-2025, the 2026 agent-framework landscape has clear leaders:
| Framework | Paradigm | Model support | Learning curve | Production readiness | Fit |
|---|---|---|---|---|---|
| LangGraph | Explicit state machine / graph | Any | Medium | Highest (LangSmith integration, checkpoint, time-travel debug, HITL) | Complex production agents; explainable flows |
| CrewAI | Role + task DSL | Any | Lowest (starts at 20 lines) | Medium | Fast business prototypes; multi-agent role collaboration |
| AutoGen | Multi-agent conversational negotiation | Any | Medium | In maintenance (Microsoft has shifted to Microsoft Agent Framework) | Maintaining legacy projects |
| OpenAI Agents SDK | Imperative handoff chain | OpenAI only | Extremely low (minimal mental model + first-class tracing) | Medium | OpenAI-ecosystem first |
| Anthropic Agent Skills | Skill packages (released in 2026) | Claude only | Low | Medium | The Claude ecosystem; reusable capabilities |
| Smolagents | Minimal (a core of a few hundred lines) | Any | Low | Low (mainly teaching / research) | Learning; the HuggingFace ecosystem |
| Mastra | TypeScript-native | Any | Low | Medium (growing fast) | JS/TS projects; Vercel / Next.js |
| Pydantic AI | Strongly typed Python | Any | Medium | Medium | Python projects where type safety matters |
| Google ADK | / | Gemini-first | Medium | Medium | The Gemini / Vertex AI ecosystem |
| LlamaIndex Agent | RAG-first | Any | Medium | Medium | RAG-heavy settings |
LangGraph vs CrewAI vs OpenAI Agents SDK is the three-way comparison discussed most in 2026:
The same task: “a ReAct agent that can answer questions with a web-search tool”. Watch how the three code philosophies differ.
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
llm = ChatAnthropic(model="claude-sonnet-4-6")
tools = [TavilySearchResults(max_results=3)]
# 一行创建预制 ReAct agent
agent = create_react_agent(llm, tools)
# 调用(自带 streaming + checkpoint)
for chunk in agent.stream({"messages": [("user", "2026 年 SWE-bench 头部模型是?")]}):
print(chunk)
# 自定义复杂图
class State(TypedDict):
messages: list
iterations: int
def should_continue(state):
if state["iterations"] >= 10: return END # max iter cap
return "agent"
graph = StateGraph(State)
graph.add_node("agent", agent)
graph.add_conditional_edges("agent", should_continue)
graph.set_entry_point("agent")
app = graph.compile(checkpointer=MemorySaver()) # checkpoint = HITL & resume 一等公民
Traits: defined as a graph, state explicit, checkpoint built in, time-travel debug.
from crewai import Agent, Task, Crew
from crewai_tools import SerperDevTool
researcher = Agent(
role="2026 AI 趋势分析师",
goal="找到关于 SWE-bench 头部模型的可靠信息",
backstory="你是科技新闻深度分析专家",
tools=[SerperDevTool()],
verbose=True,
)
writer = Agent(
role="技术博主",
goal="把分析结果写成简洁清晰的总结",
backstory="你擅长把技术细节翻译成易读文字",
verbose=True,
)
task1 = Task(description="调查 2026 年 SWE-bench 头部模型", agent=researcher)
task2 = Task(description="把调查写成 200 字总结", agent=writer)
crew = Crew(agents=[researcher, writer], tasks=[task1, task2])
result = crew.kickoff()
Traits: a three-layer DSL of role + task + crew; starts at 20 lines; the fastest to pick up.
from agents import Agent, Runner, function_tool
@function_tool
def web_search(query: str) -> str:
"""搜索网页"""
# ... 实现略
return search_results
researcher = Agent(
name="researcher",
instructions="你是研究员,用 web_search 找资料",
tools=[web_search],
)
writer = Agent(
name="writer",
instructions="把研究结果写成 200 字总结",
handoffs=[researcher], # writer 可以把任务 handoff 回 researcher 继续查
)
result = Runner.run_sync(writer, "2026 年 SWE-bench 头部模型是?")
print(result.final_output)
Traits: handoff is the SDK’s core abstraction; tracing is first-class; the mental model is minimal.
| Dimension | LangGraph | CrewAI | OpenAI Agents SDK |
|---|---|---|---|
| Abstraction | State + Graph | Role + Task + Crew | Agent + Tools + Handoff |
| Mental model | State-machine programmer | Team manager | Function calls |
| Control-flow visibility | Very high (the graph) | Medium (the task flow) | Low (imperative) |
| Debugging tools | LangSmith time-travel | Default logs | OpenAI tracing |
| Extensibility | Very strong (custom nodes) | Medium (custom tasks) | Medium (custom tools) |
| Model support | Any | Any | OpenAI only |
| Day-1 ramp-up | Requires understanding graphs | 30 minutes | 5 minutes |
| Production deployment | Strongest | Medium | Medium |
In 2026 Anthropic introduced Agent Skills — packaging the procedural knowledge of “how an agent completes a class of task” into a reusable unit. This is Anthropic’s second attempt at an open standard, after MCP.
| Abstraction | What it solves | Form | Analogy |
|---|---|---|---|
| Tools (function calling) | One atomic operation | A function signature + a JSON schema | One shell command |
| MCP | Connect external tools and data to an LLM | A server exposes tools / resources / prompts | An OS device driver |
| Agent Skills | A complete procedure for finishing a class of task | A folder (instructions + templates + scripts + checklist items) | A shell script / playbook |
The key insight: MCP connects tools to the agent; Skills teach the agent how to use those tools to finish a concrete task. In Anthropic’s words: “MCP is plumbing, Skills is procedural knowledge.”
A skill is a folder:
my-skill/
├── SKILL.md # 入口:何时用、做什么、怎么做
├── templates/ # 输出模板
│ └── report.md
├── scripts/ # 可执行辅助脚本
│ └── analyze.py
├── examples/ # 示范 input/output
│ └── case1.md
└── resources/ # 静态资源
└── compliance.json
SKILL.md is a manual for the LLM to read:
# Skill: PR Review
## When to use
当用户请求"review my PR" / "code review" 时
## What it does
检查 diff 的:
- 安全问题(SQL injection / XSS / hardcoded secret)
- 测试覆盖
- 文档同步
## How to do
1. 用 `gh pr diff` 读 diff
2. 跑 `scripts/scan_security.py` 找 secret
3. 检查每个改动的函数是否有对应 test
4. 用 `templates/review.md` 输出
## Definition of done
- 输出包含 3 个 section(必读)
- 每个发现都引用 file:line
- 严重度按 critical / major / minor 分类
While it is carrying out a task, the LLM reads SKILL.md on its own and follows the steps in it.
Claude Code supports a skills directory natively. Create .claude/skills/ at the project root:
my-project/
├── .claude/
│ └── skills/
│ ├── pr-review/SKILL.md
│ ├── deploy/SKILL.md
│ └── investigate-bug/SKILL.md
Claude Code scans it at startup. When the user says “review the pr”, it matches the pr-review skill and runs it.
Skills are the standardized unit of reusable agentic capability — a team can share them, an organization can govern them, and the community can publish them. Anthropic open-sourced the spec, the same strategy as with MCP: build the ecosystem first, then unify the standard.
For community skills, see skillmatic-ai/awesome-agent-skills.
The state machine of basic LangGraph was covered in §5.3. Below are five advanced features that production agents actually use:
LangGraph has a built-in checkpointer. After each node runs, state is saved automatically:
from langgraph.checkpoint.postgres import PostgresSaver
# 生产用 Postgres checkpointer(持久化跨进程)
checkpointer = PostgresSaver.from_conn_string("postgresql://...")
checkpointer.setup()
app = graph.compile(checkpointer=checkpointer)
# 每次调用带 thread_id
config = {"configurable": {"thread_id": "session_42"}}
result = app.invoke({"messages": [...]}, config=config)
# 服务器重启后用同一 thread_id 继续,状态自动恢复
result = app.invoke({"messages": [new_message]}, config=config)
Time-travel: you can roll back to any historical checkpoint and fork a new branch from there:
# 列出当前 thread 的所有历史 checkpoint
history = list(app.get_state_history(config))
# 选某个历史点
old_state = history[3]
# 从那里继续(fork 一条新分支)
result = app.invoke(None, config=old_state.config)
Uses: during debugging, go back to the step before the error; A/B-test two tool-call results; let the user undo an action.
from langgraph.types import interrupt, Command
def risky_action(state):
# 高风险操作前暂停等用户
user_response = interrupt({
"question": f"即将执行: DELETE FROM users WHERE id={state['user_id']}",
"type": "approval_required",
})
if user_response.get("approved"):
# 执行
db.execute(...)
return {"status": "executed"}
return {"status": "cancelled"}
# 调用时碰到 interrupt 会停下,等用户响应
result = app.invoke({"user_id": 123}, config)
# result 里有 __interrupt__ 字段表示卡在某 node
# 用户在 UI 点 approve / reject
app.invoke(Command(resume={"approved": True}), config) # 继续
Split a complex agent into subgraphs. Each team develops, tests, and deploys on its own:
# Research team 是个子图
def build_research_team():
g = StateGraph(ResearchState)
g.add_node("planner", planner)
g.add_node("searcher", searcher)
g.add_node("synthesizer", synthesizer)
# ... edges
return g.compile()
# Engineering team 同理
def build_eng_team():
...
# 顶层把两个团队当 node 嵌入
top = StateGraph(GlobalState)
top.add_node("research", build_research_team()) # ★ 子图作为 node
top.add_node("eng", build_eng_team())
top.add_node("manager", top_manager)
top.set_entry_point("manager")
# ... edges 串起来
async for event in app.astream_events({"messages": [...]}, config, version="v2"):
if event["event"] == "on_chat_model_stream":
# LLM 流式输出 token
chunk = event["data"]["chunk"].content
yield chunk
elif event["event"] == "on_tool_start":
yield f"[正在调用 {event['name']}...]\n"
elif event["event"] == "on_tool_end":
yield f"[完成 {event['name']}]\n"
Render it live on the front end over SSE or WebSocket. The user experience improves 10×.
LangSmith records every checkpoint automatically. In the dashboard, click any step and you can:
This is the core reason LangGraph pulls ahead of CrewAI and AutoGen on production maturity.
The 4 + 1 mainstream patterns for putting several agents together:
┌───────────────┐
│ Supervisor │
└───┬───┬───┬───┘
│ │ │
┌───────▼┐ ┌▼─┐ ┌▼──────┐
│ Agent A│ │B │ │ Agent C│
└────────┘ └──┘ └────────┘
A central supervisor agent receives the goal → splits it into subtasks → routes them to specialist worker agents → synthesizes the result. Easy to reason about, easy to trace, and the control flow is clear — one routing node, and every decision is visible. The mainstream production architecture.
Agent A → Agent B → Agent C → Output
Agents run in order. The output of one is the input of the next. In essence this is like a traditional ETL workflow. The “agent” abstraction is a bit redundant in this pattern; the benefit is that it integrates smoothly with traditional systems.
Agent A ←──→ Agent B
↓ ↘ ↗ ↑
↑ ╲╱ ↓
Agent C ←──→ Agent D
Decentralized: agents hand off directly to one another. No intermediary, fewer calls, faster — but hard to debug, and easy to fall into a loop of passing the buck. LangGraph makes both supervisor and swarm first-class, so you choose by scenario.
Top Manager
├── Mid Supervisor 1
│ ├── Worker A
│ └── Worker B
└── Mid Supervisor 2
├── Worker C
└── Worker D
Tree-shaped, multi-level delegation: the top handles strategy, the middle handles tactics, and the leaves execute. With shared state management, conflicts drop by a large margin (how large varies widely by team and by task; precise figures of the “80%” kind have no credible basis in public sources). It fits complex business, and the latency is high.
In practice it is often a hybrid: one layer of a hierarchy uses a swarm for parallel exploration, and another layer uses a supervisor to converge. Devin’s internals, for example, are hierarchical (planner → coder → reviewer, three layers) plus a local swarm (the reviewer spawns several critics).
Each of the five code samples below runs on its own (you need pip install langgraph langchain-anthropic) and covers one of the five collaboration patterns.
from langgraph.graph import StateGraph, END
from typing import TypedDict, Literal
from langchain_anthropic import ChatAnthropic
class State(TypedDict):
task: str
next_worker: str
results: dict
llm = ChatAnthropic(model="claude-sonnet-4-6")
def supervisor(state: State):
"""中央 supervisor 决定派给哪个 worker"""
decision = llm.invoke(f"""任务:{state["task"]}
路由到下面哪个专家?只回答名字:
- researcher(找资料)
- writer(写文章)
- coder(写代码)
- DONE(已完成)""")
return {"next_worker": decision.content.strip()}
def researcher(state: State):
result = llm.invoke(f"找资料:{state['task']}")
return {"results": {**state["results"], "research": result.content}}
def writer(state: State):
result = llm.invoke(f"写文章基于:{state['results']}")
return {"results": {**state["results"], "draft": result.content}}
def coder(state: State):
result = llm.invoke(f"写代码:{state['task']}")
return {"results": {**state["results"], "code": result.content}}
def route(state: State) -> Literal["researcher", "writer", "coder", END]:
return END if state["next_worker"] == "DONE" else state["next_worker"]
g = StateGraph(State)
g.add_node("supervisor", supervisor)
g.add_node("researcher", researcher)
g.add_node("writer", writer)
g.add_node("coder", coder)
g.set_entry_point("supervisor")
g.add_conditional_edges("supervisor", route)
for w in ["researcher", "writer", "coder"]:
g.add_edge(w, "supervisor") # worker 完成后回 supervisor
app = g.compile()
result = app.invoke({"task": "调查 2026 RAG 现状并写一段总结", "results": {}})
class PipelineState(TypedDict):
raw_data: str
cleaned: str
analyzed: str
summary: str
def clean(state):
return {"cleaned": llm.invoke(f"清洗:{state['raw_data']}").content}
def analyze(state):
return {"analyzed": llm.invoke(f"分析:{state['cleaned']}").content}
def summarize(state):
return {"summary": llm.invoke(f"摘要:{state['analyzed']}").content}
g = StateGraph(PipelineState)
g.add_node("clean", clean)
g.add_node("analyze", analyze)
g.add_node("summarize", summarize)
g.set_entry_point("clean")
g.add_edge("clean", "analyze")
g.add_edge("analyze", "summarize")
g.add_edge("summarize", END)
app = g.compile()
from langgraph.graph import MessagesState
from langgraph.prebuilt import create_react_agent
from langchain_core.tools import tool
@tool
def transfer_to_researcher():
"""需要查资料时调用"""
return "Transferring to researcher"
@tool
def transfer_to_writer():
"""需要润色时调用"""
return "Transferring to writer"
researcher = create_react_agent(llm, tools=[search_tool, transfer_to_writer])
writer = create_react_agent(llm, tools=[transfer_to_researcher])
def router(state: MessagesState) -> Literal["researcher", "writer", END]:
last = state["messages"][-1]
if hasattr(last, "tool_calls") and last.tool_calls:
name = last.tool_calls[0]["name"]
if "researcher" in name: return "researcher"
if "writer" in name: return "writer"
return END
g = StateGraph(MessagesState)
g.add_node("researcher", researcher)
g.add_node("writer", writer)
g.set_entry_point("researcher")
g.add_conditional_edges("researcher", router)
g.add_conditional_edges("writer", router)
app = g.compile()
Each agent decides for itself whom to hand off to. There is no central supervisor.
# 顶层 manager
def top_manager(state):
decision = llm.invoke(f"任务:{state['task']}\n路由:team_research / team_eng / DONE")
return {"next_team": decision.content.strip()}
# 中层 team supervisor
def research_team_supervisor(state):
sub = llm.invoke("派给 worker:searcher / analyst / DONE")
return {"next_research_worker": sub.content.strip()}
def eng_team_supervisor(state):
sub = llm.invoke("派给 worker:coder / reviewer / DONE")
return {"next_eng_worker": sub.content.strip()}
# 底层 worker(略)
# 编排:顶层 → 中层 supervisor → 底层 worker → 中层 supervisor → 顶层
g = StateGraph(...)
g.add_node("top", top_manager)
g.add_node("research_team", build_research_subgraph()) # 子图嵌入
g.add_node("eng_team", build_eng_subgraph())
g.set_entry_point("top")
g.add_conditional_edges("top", route_top)
# 子图返回 → 顶层
g.add_edge("research_team", "top")
g.add_edge("eng_team", "top")
LangGraph’s subgraph (subgraph) feature lets each layer be tested on its own and deployed on its own.
# 顶层 hierarchical → 中层用 supervisor → 个别节点用 swarm 做并行探索
g = StateGraph(...)
# 顶层 hierarchical
g.add_node("strategist", strategist)
g.add_node("execution_layer", execution_supervisor_subgraph)
# 中层 supervisor
def execution_supervisor_subgraph():
sub = StateGraph(...)
sub.add_node("supervisor", supervisor)
sub.add_node("planner", planner)
sub.add_node("explorer_swarm", swarm_subgraph) # 嵌入 swarm
sub.add_node("validator", validator)
return sub.compile()
# 底层 swarm 做并行假设探索
def swarm_subgraph():
sub = StateGraph(...)
for agent_id in ["explorer_a", "explorer_b", "explorer_c"]:
sub.add_node(agent_id, build_explorer(agent_id))
sub.add_node("synthesizer", synthesizer)
# 三个 explorer 并行,结果汇到 synthesizer
return sub.compile()
In practice, the sub-agent systems of Devin, Manus, and Claude Code are all close to this hybrid topology: strategy at the top, a tactical supervisor in the middle, and parallel exploration at the bottom.
| Benchmark | What it evaluates | Task scale | Scoring | Head of the 2026-04 ranking |
|---|---|---|---|---|
| SWE-bench Verified | Repairing real GitHub issues | 500 human-verified issues from Python projects | Programmatic (run the tests) | Claude Opus 4.7 ~ 87% (Anthropic’s announced figure; the latest numbers are on the swebench.com leaderboard) |
| GAIA (Princeton HAL) | Multi-step reasoning for a general assistant | 466 questions, three difficulty tiers | Programmatic + LLM judge | Claude Sonnet 4.5 = 74.6%; Anthropic swept the top 6 |
| TAU-bench | Tool use + dialogue with a real user | Multiple domains | Simulated multi-turn interaction | — |
| AgentBench | 8 environment classes (OS / DB / KG / card games / lateral thinking / household chores / online shopping / web) | Cross-environment | Scored per environment | — |
| WebArena (CMU) | Web-navigation agents | 812 tasks / 5 websites + a map | Programmatic | — |
| OSWorld | Real operation across operating systems and applications | 369 tasks / Ubuntu + Windows + macOS | Verified by execution | — |
SWE-bench Verified is the coding-agent signal most trusted in 2026: 500 human-verified issues from real GitHub Python projects. The agent has to read the code, edit files, and make the tests pass.
Claude Opus 4.7 is reported as one of the current leaders on SWE-bench Verified (the latest score is on swebench.com). OpenAI o3 / the Codex series and Google Gemini Code follow. The strongest open-source models currently sit in the 60%+ band.
Berkeley RDI’s How We Broke Top AI Agent Benchmarks systematically audited 8 mainstream benchmarks (SWE-bench, WebArena, OSWorld, GAIA, Terminal-Bench, FieldWorkArena, CAR-bench, and others). The conclusion: every one of them can be exploited to a near-perfect score without actually completing the task. Common hack paths:
What this means in practice:
If you want to run SWE-bench Verified yourself, and not only read the leaderboard number, the full procedure is below.
阶段 A: 安装 + 准备
pip install swebench
docker pull princeton-nlp/swebench-eval:latest
阶段 B: 跑你的 agent,把结果生成为 predictions JSON
你的 agent → 输入:每个 task 的 problem_statement
→ 输出:patch 文件(diff 格式)
→ 保存为 predictions.jsonl
阶段 C: 用 SWE-bench harness 评估
python -m swebench.harness.run_evaluation \
--dataset_name princeton-nlp/SWE-bench_Lite \
--predictions_path ./predictions.jsonl \
--max_workers 8 \
--run_id my_agent_v1
{"instance_id": "django__django-11099", "model_name_or_path": "my_agent_v1",
"model_patch": "diff --git a/django/...\n--- a/django/...\n+++ b/django/...\n@@ -100,3 +100,5 @@\n+ # fix: ...\n"}
{"instance_id": "django__django-11133", "model_name_or_path": "my_agent_v1",
"model_patch": "..."}
Each line is one task. model_patch is a unified diff. The harness applies the diff, runs the tests, and compares the result with what was expected.
import json
from datasets import load_dataset
def run_my_agent_on_swe_bench():
dataset = load_dataset("princeton-nlp/SWE-bench_Lite", split="test")
predictions = []
for task in dataset:
# 准备 agent 上下文
context = {
"repo": task["repo"], # e.g., "django/django"
"base_commit": task["base_commit"],
"problem_statement": task["problem_statement"],
"hints_text": task["hints_text"], # ★ 注意:避免泄露 ground truth
}
# 跑你的 agent(带 max_iterations / cost_cap 等护栏)
try:
patch = my_agent.solve(context, max_iter=30, max_cost_usd=2.0)
except Exception as e:
patch = "" # 失败也要交白卷
predictions.append({
"instance_id": task["instance_id"],
"model_name_or_path": "my_agent_v1",
"model_patch": patch,
})
with open("predictions.jsonl", "w") as f:
for p in predictions:
f.write(json.dumps(p) + "\n")
if __name__ == "__main__":
run_my_agent_on_swe_bench()
The SWE-bench team’s mini-swe-agent is a minimal agent harness whose only tool is bash, about 500 lines of Python, meant as a baseline to compare against:
pip install mini-swe-agent
mini-swe-agent run --model claude-sonnet-4-6 --dataset princeton-nlp/SWE-bench_Lite
If your agent cannot beat the mini-swe-agent baseline on SWE-bench Lite, suspect the harness implementation before you suspect the model.
| Variant | Tasks | Estimated cost to finish (Claude Sonnet 4.6) | Use |
|---|---|---|---|
| SWE-bench Lite | 300 | ~$30, ~4 hours | Fast iteration / day-to-day |
| SWE-bench Verified | 500 | ~$60, ~8 hours | Papers / leaderboard submission |
| SWE-bench full | 2,294 | ~$300, ~36 hours | Rarely used |
In practice: use Lite day to day, and run Verified before a formal release.
A public benchmark does not necessarily fit your business. The eval you can actually trust is ground truth from your own business. The minimum viable procedure:
Collect 100 real business scenarios:
Store them with the schema below:
[
{
"id": "case_001",
"category": "refund_query",
"difficulty": "easy",
"input": {"messages": [{"role": "user", "content": "我要退订单 #12345"}]},
"expected_behavior": [
"调用 lookup_order(12345)",
"调用 process_refund(...)",
"回复用户告知预计 3-5 工作日",
],
"forbidden_behavior": [
"不能在没有 lookup 的情况下直接 refund",
"不能透露其他用户订单信息",
],
"ground_truth_answer": "您的订单 #12345 退款已发起...",
},
...
]
def evaluate_agent(agent_fn, golden_set):
results = []
for case in golden_set:
# 跑 agent 收集 trace
with TraceCapture() as trace:
answer = agent_fn(case["input"])
# 三层评估
scores = {
# 层 1:tool call 顺序对不对
"tool_correctness": check_tool_calls(trace, case["expected_behavior"]),
# 层 2:是否触发禁止行为
"safety": not any(matches(trace, fb) for fb in case["forbidden_behavior"]),
# 层 3:答案质量(LLM-as-judge)
"answer_quality": llm_judge(case["input"], answer, case["ground_truth_answer"]),
}
results.append({"case_id": case["id"], **scores})
return results
# .github/workflows/eval.yml
name: Agent Eval Gate
on: pull_request
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -e .
- run: python -m my_agent.eval --golden-set ./eval/golden.jsonl --output report.json
- run: |
python -c "
import json
r = json.load(open('report.json'))
tc = sum(x['tool_correctness'] for x in r) / len(r)
sf = sum(x['safety'] for x in r) / len(r)
aq = sum(x['answer_quality'] for x in r) / len(r)
print(f'tool_correctness={tc:.2f}, safety={sf:.2f}, answer_quality={aq:.2f}')
assert tc >= 0.85, 'tool_correctness regression'
assert sf >= 0.99, 'safety regression'
assert aq >= 0.75, 'quality regression'
"
Every pull request runs the suite. A regression on any metric blocks the merge.
A public leaderboard number is only the surface. What a production agent evaluation should actually look at:
If you are writing code in 2026, you are almost certainly using one of these three: Claude Code, GitHub Copilot, Cursor. The most common two-tool stack is Cursor for daily editing + Claude Code for hard tasks, or Copilot in the IDE + Claude Code in the terminal.
Claude Code (Anthropic) — the most representative CLI agent of 2026
CLAUDE.md file stores project-level instructions, conventions, and architecture notes, and persists across sessions (one form of long-term memory)Cursor
GitHub Copilot Workspace
Devin (Cognition)
Manus
Anthropic Claude Computer Use
OpenAI Codex Background Computer Use
Operator (OpenAI’s earlier computer-use product)
Anthropic Computer Use, Operator, OpenAI CUA, and Manus all run the same perceive–decide–act loop:
┌─────────────────────────────────────────────────────────┐
│ ① 截图(screenshot tool) │
│ resolution: 1280x800(Anthropic 推荐) │
│ 格式: PNG → base64 → vision LLM input │
└─────────────────────────────┬───────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ ② VLM 推理(Vision LLM) │
│ - 解析当前界面(按钮 / 输入框 / 文本 / 图标) │
│ - 决定下一步动作("点击右上角的『下一步』按钮") │
│ - 输出结构化 action:{type, x, y, text, key} │
└─────────────────────────────┬───────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ ③ 执行(在 sandbox VM / 远程桌面 / Chrome extension) │
│ - mouse_move(x, y) / click / drag │
│ - type_text("...") / key_press("ctrl+c") │
│ - 100-500ms 后截下一张图 │
└─────────────────────────────┬───────────────────────────┘
│ 循环
└──────────────────────────▶
The hard engineering problems:
| Problem | What you see | What the field does about it |
|---|---|---|
| Pixel coordinates miss | The LLM says “click (450, 320)” and the real target is at (445, 318). Five pixels, and the wrong button | Anthropic strengthens the grounding loss at training time. At runtime, allow a tolerance and verify with a second screenshot |
| Dynamic / asynchronous UI | The page is still loading after the click, so the next screenshot is an intermediate state | Introduce wait_for_idle, plus the DOM / accessibility tree as a second signal (pixels alone are not enough) |
| CAPTCHA / anti-bot | Almost every CAPTCHA stops the agent | The field has given this up: either HITL, so the user solves it, or switch to an API path |
| Multiple windows / focus drift | A notification pops up, or the app switches, and the agent loses context | Capture the full screen and keep a window_id. OpenAI Operator isolates a dedicated browser |
| PII and privacy leakage | The screenshot sends passwords, mail, and messages that are on screen to the LLM | Mask sensitive regions on the client; filter with local OCR; use a dedicated computer-use VM |
| Latency and cost | 1–3 seconds per step, and 1–2K vision tokens per screenshot. Ten minutes easily runs past $5 | Screenshot on an interval, lower the resolution, cache UI-element detection, and prefer the DOM over vision |
Mainstream reliability numbers (OSWorld, 2026-04):
So at the end of 2026, computer use is still research and assistance, not a production replacement. Before it sits on a critical product path, there has to be a HITL fallback.
Aider
Cline / Continue / Roo Code
90% of production agents die here. The agent keeps retrying the same failed action inside some loop, burning tokens and API quota until the budget alarm fires.
Real cases:
| Shape | How it triggers | Example | What makes it fatal |
|---|---|---|---|
| Tool Retry Loop | The tool keeps returning the same error, and the agent keeps trying the same arguments | read_file('config.yaml') → FileNotFoundError, and the agent retries 50 times |
The LLM has no strong signal that it already tried this; the error message carries no diagnosis |
| Test-Fail-Fix-Break | A change at A makes test B pass, and the new change breaks test C. Around it goes | npm test is red → edit → a different test goes red → edit again → … |
There is no dashboard of the whole test state. Each step sees only a local slice |
| Hallucinated Tool / API Loop | The agent calls a tool or API that does not exist, then nudges the name and tries again | search_web → missing → web_search → do_search → … |
The tool list has no enforced schema, and the error does not return the list of available tools |
| Plan-Replan Spiral | After every failure the agent rewrites the whole plan, and the record of the old failure is lost | “Replan” makes the LLM redesign the same failing steps | There is no episodic memory of “X was already tried and did not work” |
| Approval Wait Deadlock | The agent waits for HITL approval, and nobody is looking at the UI | request_human_approval fired, nobody responded, and the agent sat for hours |
There is no approval timeout and no escalation |
An internal Anthropic analysis reports that about 90% of Loops of Death are the cycle “thinking → tool call → the same error → the same thinking → the same tool call.” The LLM has no explicit memory of “I just tried this,” so it keeps recommending the same action.
Four defenses. All four are required:
# 1. 硬上限:iterations / timeout / token / cost
MAX_STEPS = 25
MAX_DURATION = 300 # seconds
MAX_TOKENS = 200_000
MAX_COST_USD = 5.0
# 2. 重复检测:相同 tool + 相同参数 ≥ N 次 → 强制 break
def detect_repeat(history, tool_call, n=3):
same = [h for h in history[-10:] if h.tool == tool_call.tool
and h.args == tool_call.args and h.error == tool_call.error]
return len(same) >= n
# 3. 错误信号注入:把"已失败 N 次"用强语言注入下一轮 prompt
if detect_repeat(history, tool_call, n=2):
inject = f"⚠️ You have called {tool_call.tool}({tool_call.args}) "
f"and got the same error '{tool_call.error}' {n} times. "
f"Do NOT repeat. Choose a different approach OR stop and report."
next_messages.append({"role": "system", "content": inject})
# 4. Escape hatch:明确给 agent "放弃 / 报告" 工具
tools.append({
"name": "give_up_and_report",
"description": "Use when stuck. Reports what you tried and asks user.",
"parameters": {"reason": "string", "attempted": "list"}
})
Engineering tradeoffs:
read_file. In practice the fingerprint has to include “the same error,” and read-only tools need a looser threshold. Otherwise the escape hatch fires by mistake, and the agent gives up on a task it could have finished.One invented fact triggers a chain of bad downstream calls.
A real case:
Countermeasures:
The agent calls a highly destructive tool by mistake.
A 2026 collection of real incidents (as reported in the press; readers should check any specific accusation against a vendor against the primary source linked in the bibliography):
Every one of them is the same combination failing at once: “the agent held permissions it should not have held + no deny-first default + no sandbox + no HITL gate.”
Countermeasures: see §10.
Silent failures (failures with no alert) are the worst kind. The agent quietly abandons the task, returns an empty result, or opens a PR that looks finished and is wrong. This class has to be watched continuously in production, with observability plus eval.
Whatever the failure, it shows up as money. A production agent needs:
Anthropic’s Building Effective AI Agents, plus later engineering posts, collapses production-agent failures into 4 classes. They overlap §9.1–§9.5 above, from a different angle: organized by the fix.
| Failure mode | How it shows up | Root cause | The fix that goes with it |
|---|---|---|---|
| ① Tool misuse | The wrong tool / the right tool with illegal arguments / a highly destructive tool | The tool description is vague, the parameter schema is loose, and there is no pre-check | (1) A strict JSON schema, plus a second check on the server; (2) requires_confirmation: true on highly destructive tools; (3) a PreToolUse hook blocks before execution; (4) the tool description states when not to use it |
| ② Hallucinated tool / hallucinated API | Calling a tool that does not exist at all, or inventing an API | The model guesses the tool list instead of reading it; the error message does not steer back to the right tool | (1) Attach an available_tools list when returning an error; (2) constrained decoding, so the tool name is chosen from an allowlist; (3) deny-by-default: an unregistered tool is rejected immediately, with no retry |
| ③ Context explosion | Tool results, history, and intermediate thinking blow the context up | No windowing; tool results are not trimmed; old steps are not summarized | (1) Graded trimming of tool results (see context-engineering.en.md §2.6.3); (2) periodic compaction of old steps; (3) sub-agent isolation, so a long-running task uses a child context window |
| ④ Reward Hacking / Goal Misgen | The agent meets the goal in form and violates the intent (delete tests so CI passes, or write a mock and pretend it is implemented) | The verifier signal is incomplete or gameable; there is no structured feedback | (1) The verifier has to be multi-signal (test pass + diff size + lint + behavior diff); (2) a second look with the Evaluator-Optimizer pattern; (3) HITL spot checks + a long-running audit |
What Anthropic’s engineering experience adds up to (from production):
Claude Code’s default security posture is deny-first plus human escalation: deny rules override ask rules, ask rules override allow rules, and an unrecognized action escalates to the user rather than being let through quietly.
The concrete “independent layers of guardrails”:
Any one layer can block an action. That is real defense in depth.
| Mode | Meaning | Where it fits |
|---|---|---|
| HITL (human in the loop) | A human must confirm before the critical step | High risk (medical, finance, deleting data) |
| HOTL (human on the loop) | A human is watching and does not block the run; they step in at the critical moment | Medium risk (coding, content generation) |
| Fully autonomous | Report only when the work is finished | Low risk (data lookup, reports) |
Anthropic’s auto-mode data: users click approve on ~93% of permission prompts, without distinction.
So approval by itself has been made behaviorally unreliable. People habitually click yes. The conclusion:
| Option | Isolation strength | Startup latency | Memory overhead | Compatibility | Fits |
|---|---|---|---|---|---|
| MicroVM (Firecracker / AWS Fargate / Kata) | Strongest (its own Linux kernel + KVM hardware virtualization) | ~125 ms | < 5 MiB / VM | Full Linux | High-security production (running a stranger’s code) |
| gVisor | Strong (the user-space Sentry kernel + syscall interception) | ~50 ms | ~15 MiB | Most syscalls | The balance, plus GPU workloads |
| Hardened container | Medium (a shared host kernel + several restrictions) | < 10 ms | Negligible | Full | Code you wrote yourself |
| Plain Docker | Weak (namespaces + cgroups only) | ~10 ms | Negligible | Full | Must not run untrusted LLM-generated code |
| Process-level (seccomp / chroot) | Very weak | < 1 ms | 0 | Full | The last-resort backstop |
The industry consensus (from e2b, Daytona, Modal, Vercel Sandbox, and the other mainstream options): a container alone is not enough. Move up to a microVM or to gVisor. Nearly 30 years of Linux kernel attack surface is a sieve against LLM-generated code. One namespace-escape CVE jumps from the sandbox to the host.
Architecture: each sandbox is its own Linux kernel plus a full init process, running under KVM hardware virtualization. To get host root, an attacker first has to break through the kernel, the KVM hypervisor, and the Firecracker VMM — three layers.
A minimal boot config:
# 1. 准备 kernel 和 rootfs
wget https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/x86_64/kernels/vmlinux.bin
wget https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/x86_64/rootfs/bionic.rootfs.ext4
# 2. 启动 Firecracker
firecracker --api-sock /tmp/firecracker.sock &
# 3. 配置 microVM(curl REST API)
curl -X PUT --unix-socket /tmp/firecracker.sock http://localhost/boot-source \
-H 'Content-Type: application/json' \
-d '{"kernel_image_path":"./vmlinux.bin","boot_args":"console=ttyS0 reboot=k panic=1"}'
curl -X PUT --unix-socket /tmp/firecracker.sock http://localhost/drives/rootfs \
-d '{"drive_id":"rootfs","path_on_host":"./bionic.rootfs.ext4",
"is_root_device":true,"is_read_only":false}'
# 资源限制(必填)
curl -X PUT --unix-socket /tmp/firecracker.sock http://localhost/machine-config \
-d '{"vcpu_count":1,"mem_size_mib":256}'
# 网络(强烈建议默认拒绝出网)
curl -X PUT --unix-socket /tmp/firecracker.sock http://localhost/network-interfaces/eth0 \
-d '{"iface_id":"eth0","host_dev_name":"tap0"}'
# 启动
curl -X PUT --unix-socket /tmp/firecracker.sock http://localhost/actions \
-d '{"action_type":"InstanceStart"}'
What it takes to run this in production:
firecracker-containerd to put an OCI interface on it, so agent code looks like it is running DockerE2B, Daytona, and Modal are all Firecracker wrappers inside, which saves you from operating the cluster yourself:
# E2B(最常用 LLM agent sandbox)
from e2b_code_interpreter import Sandbox
with Sandbox.create(timeout=30) as sbx: # ★ 必须设 timeout
result = sbx.run_code("print(2+2)")
Architecture: a user-space process, Sentry, sits between the application and the host kernel, intercepts every syscall, and reimplements it in user space. The application looks like it is running on Linux. It can only call the subset Sentry implements.
Compared with Firecracker:
A minimal config (OCI runtime):
# 1. 安装 gVisor runsc
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list > /dev/null
sudo apt update && sudo apt install -y runsc
# 2. 把 runsc 注册为 Docker 的 runtime
sudo runsc install
sudo systemctl restart docker
# 3. 跑容器时指定 runtime
docker run --runtime=runsc --rm -it python:3.12 python -c "print('hello from gVisor')"
Kubernetes integration (RuntimeClass):
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
---
apiVersion: v1
kind: Pod
metadata:
name: agent-sandbox
spec:
runtimeClassName: gvisor # ★ 这个 pod 跑在 gVisor 里
containers:
- name: code-runner
image: python:3.12
resources:
limits: {memory: "256Mi", cpu: "500m"}
When this applies: you are running code you wrote (trusted), and you only need to stop accidental damage. Do not run untrusted LLM-generated code in a hardened container. Community consensus has already rejected that.
The minimum hardening config (every item is required):
docker run \
--rm \
--user 1000:1000 \ # 非 root
--read-only \ # 文件系统只读
--tmpfs /tmp:size=64m,noexec \ # 临时空间不可执行
--cap-drop ALL \ # 丢掉所有 capabilities
--security-opt no-new-privileges \ # 禁止 setuid
--security-opt seccomp=./seccomp.json \ # 自定义 syscall 白名单
--security-opt apparmor=docker-default \ # AppArmor profile
--network=none \ # 默认无网
--pids-limit 64 \ # 进程数上限
--memory 256m \ # 内存上限
--cpus 0.5 \ # CPU 上限
--memory-swap 256m \ # 禁用 swap
python:3.12 python /code/script.py
Rootless Docker / Podman also runs the Docker daemon inside a user namespace, which takes one more layer off the attack surface.
A custom seccomp allowlist (deny every syscall except those explicitly allowed):
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{"names": ["read", "write", "openat", "close", "fstat", "mmap", "munmap",
"brk", "rt_sigaction", "rt_sigprocmask", "ioctl", "exit_group",
"execve", "wait4", "uname", "fcntl", "lseek", "stat", "newfstatat"],
"action": "SCMP_ACT_ALLOW"}
/* 拒绝:socket, connect, ptrace, mount, kexec_load, init_module, ... */
]
}
你跑什么代码?
│
├─ 陌生人/LLM 生成的 untrusted code
│ ├─ 需要 GPU / 兼容更好 → gVisor
│ └─ 极致隔离/合规 → Firecracker microVM
│
├─ 你自己写的代码 + 防意外
│ └─ 加固容器(rootless + seccomp + cap-drop + read-only)
│
└─ 测试/开发 → 普通 Docker(不要进生产)
Defense in depth: 5 layers (if one gives way, another is still there):
1. ┌───────────────────────────┐
│ Network egress allowlist │ ← 默认拒绝出网,仅放行 GitHub/PyPI 等
└───────────────────────────┘
2. ┌───────────────────────────┐
│ Resource quota(CPU/MEM) │ ← 防 fork bomb / OOM
└───────────────────────────┘
3. ┌───────────────────────────┐
│ Time / step / cost cap │ ← 防 Loop of Death (§9.1)
└───────────────────────────┘
4. ┌───────────────────────────┐
│ Sandbox isolation │ ← Firecracker / gVisor
└───────────────────────────┘
5. ┌───────────────────────────┐
│ Audit log + alerting │ ← 异常 syscall / 出网 / 高 cost
└───────────────────────────┘
| Service | Underneath | How you attach | Price |
|---|---|---|---|
| E2B | Firecracker | Python/TS SDK, the easiest to attach | Free, plus per vCPU-minute |
| Modal | gVisor + Firecracker | A Python decorator | Billed per GPU-second |
| Daytona | Firecracker | Dev-container style | Open source + cloud |
| Vercel Sandbox | Firecracker | Native to the Next.js ecosystem | Monthly |
| AWS Fargate / Lambda | Firecracker | AWS-native | Usage-based |
| Together Code Sandbox | gVisor | API | Per call |
| CodeSandbox | Firecracker | A dev IDE | Free + a subscription |
Practical experience: start a new project on E2B or Modal, and skip the 4–6 weeks of operating your own Firecracker cluster. Revisit self-hosting once you are at scale.
The industry is moving toward selective autonomy, not fully unsupervised autonomy.
What that means: the same agent should have a different autonomy level on different actions. Reading a README can be fully autonomous. Running tests can be auto-approved. Editing a file needs review. Deleting a file needs a second human confirmation. Operating a production database stays deny-first.
How well the tools are designed directly sets agent accuracy. Plenty of teams grind on the prompt and the framework and get nowhere; rewriting how the tools are described lifts accuracy by 20%+.
1. Name it “verb + noun + object”
❌ search() / get_data() / process()
✅ search_github_issues(repo, query)
list_pending_orders_by_user(user_id)
send_slack_message_to_channel(channel, text)
When the LLM picks a tool, it mostly looks at the name. A vague name means low recall.
2. Write the description as “when to use it / when not to use it”
@tool
def cancel_order(order_id: str, reason: str) -> dict:
"""取消未发货的订单。
何时用:
- 用户明确要求取消订单
- 订单状态为 pending / preparing
- 用户已经登录验证
何时不用:
- 订单已发货(用 request_return 走退货流程)
- 用户未登录(先走 require_login)
- 仅是询问订单状态(用 get_order_status)
参数:
- order_id: 订单 ID(如 'ORD-12345')
- reason: 取消原因(用户提供的文本)
返回:
- {"status": "cancelled" | "failed", "refund_amount": float, "message": str}
错误:
- OrderNotFound: 订单不存在 → 让用户检查 ID
- OrderAlreadyShipped: 已发货 → 引导走 request_return
"""
3. The parameter schema is strict, and it has examples
Use Pydantic / JSON Schema, with strong types:
from pydantic import BaseModel, Field
from typing import Literal
class CancelOrderInput(BaseModel):
order_id: str = Field(..., description="订单 ID,格式 'ORD-数字'", pattern=r"^ORD-\d+$",
examples=["ORD-12345"])
reason: str = Field(..., description="取消原因", min_length=2, max_length=200,
examples=["用户改变主意", "找到更便宜的"])
notify_user: bool = Field(True, description="是否通知用户")
The examples field materially helps the LLM choose arguments and format them.
4. An error return should tell the model what to do next
# ❌ 不够
return {"error": "OrderNotFound"}
# ✅ 引导式
return {
"error": "OrderNotFound",
"message": "订单 ORD-12345 不存在",
"suggestions": [
"用 list_user_orders(user_id) 列出该用户所有订单",
"提示用户检查订单号格式",
],
}
When the LLM sees suggestions, it tries the next step on its own, which avoids a Loop of Death.
5. A cap on tool count: do not give a single agent more than 20 tools
The probability that an LLM picks the wrong tool rises sharply once there are more than 20 tools (research shows accuracy dropping 20%+ at 40+ tools). The production pattern:
| Anti-pattern | What actually happened | The fix |
|---|---|---|
One query_database(sql) handed to the LLM |
The LLM writes dangerous SQL, and a table gets dropped | Split it into dedicated tools such as lookup_user_by_email and get_order_status |
Tool names that are synonyms (fetch_user + get_user) |
The LLM picks at random, and the results are inconsistent | Merge them |
| Long results are not truncated | 10K rows come back, and the context blows up | Cap on the server, and add pagination |
| No timeout | The tool hangs, so the agent hangs | Force a 30s timeout |
| Side effects are undeclared | The model calls it as if it were read-only, and something gets modified | The description states “this modifies X”, and add require_confirmation |
| A long JSON result nested 7 levels deep | The LLM cannot parse it | Flatten it, and add a summary field |
Put the agent back into the picture of the four engineering layers:
┌─────────────────────────────────────────────────────────────┐
│ Harness Engineering(驾驭工程,第 4 层) │
│ 工具集成 / 沙箱 / 网关 / 推理 / 观测 / 评估 / 护栏 │
│ ─── 提供 agent 运行的"环境与约束" │
├─────────────────────────────────────────────────────────────┤
│ Agent Engineering(Agent 工程,第 3 层)← 本章 │
│ 范式 / 框架 / 多 agent / 评估 / 失败模式 / 安全权限 │
│ ─── 让 LLM 自主完成多步任务 │
├─────────────────────────────────────────────────────────────┤
│ Context Engineering(上下文工程,第 2 层) │
│ 长上下文 / RAG / 记忆 / MCP │
│ ─── 提供 agent "看到什么" │
├─────────────────────────────────────────────────────────────┤
│ Prompt Engineering(提示词工程,第 1 层) │
│ Transformer / 训练栈 / 解码 / CoT / ReAct / 自动化 │
│ ─── 提供 agent "怎么思考"的模板 │
└─────────────────────────────────────────────────────────────┘
The four layers stack. Each one is still required:
The concepts above, wired together. Below is a production-grade minimal skeleton: LangGraph + tools + retrieval + memory + a max-iteration cap + a cost cap + observability.
from typing import TypedDict, Annotated, Sequence
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_core.tools import tool
from mem0 import Memory
import langfuse_callback # observability
import time
# ============ State ============
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
user_id: str
iterations: int
total_cost_usd: float
started_at: float
# ============ 工具:知识库检索 ============
@tool
def kb_search(query: str) -> str:
"""检索企业知识库回答用户问题"""
# 调 §2.4 RAG pipeline,省略
return rag_chain.invoke({"input": query})["answer"]
@tool
def create_ticket(user_id: str, summary: str, severity: str) -> str:
"""无法回答时创建工单。severity: low/medium/high/critical"""
if severity == "critical":
# critical 必须 HITL,不允许 agent 自主创建
return "ERROR: critical 工单需人工创建"
ticket_id = ticket_system.create(user_id, summary, severity)
return f"工单 {ticket_id} 已创建"
# ============ 长期记忆 ============
memory = Memory.from_config({"vector_store": {"provider": "qdrant"}})
# ============ LLM + 工具绑定 + observability ============
llm = ChatAnthropic(
model="claude-sonnet-4-6",
callbacks=[langfuse_callback.LangfuseCallback()],
).bind_tools([kb_search, create_ticket])
# ============ Guardrail:输入侧(伪代码)============
def input_guardrail(messages):
last_user = messages[-1].content
if guardrail.detect_prompt_injection(last_user):
raise ValueError("输入触发 prompt injection 防护")
if guardrail.contains_pii(last_user):
last_user = guardrail.redact_pii(last_user)
messages[-1] = HumanMessage(content=last_user)
return messages
# ============ Agent 节点 ============
def agent_node(state: AgentState):
# 1. 输入 guardrail
messages = input_guardrail(state["messages"])
# 2. 注入历史记忆
user_memories = memory.search(query=messages[-1].content, user_id=state["user_id"])
memory_context = "\n".join([m["text"] for m in user_memories[:5]])
sys_msg = SystemMessage(content=f"你是 ACME 公司客服。用户历史:\n{memory_context}")
# 3. LLM 决策(思考 + 工具调用)
response = llm.invoke([sys_msg] + list(messages))
# 4. 更新成本(每个 provider 都返回 token usage)
cost = response.response_metadata.get("usage", {}).get("total_cost_usd", 0)
return {
"messages": [response],
"iterations": state["iterations"] + 1,
"total_cost_usd": state["total_cost_usd"] + cost,
}
# ============ 工具节点 ============
from langgraph.prebuilt import ToolNode
tool_node = ToolNode([kb_search, create_ticket])
# ============ 流程控制 ============
def should_continue(state: AgentState):
# ★★★ Loop of Death 防御:四道闸门 ★★★
if state["iterations"] >= 10:
return END # max iter cap
if state["total_cost_usd"] >= 0.50:
return END # cost cap $0.5/会话
if time.time() - state["started_at"] >= 60:
return END # 超时 60s
last_msg = state["messages"][-1]
if not last_msg.tool_calls:
return END # 没新工具调用 = 完成
return "tools"
# ============ 构图 ============
graph = StateGraph(AgentState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
graph.set_entry_point("agent")
graph.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
graph.add_edge("tools", "agent")
# ============ 编译(带 checkpoint 支持 HITL)============
from langgraph.checkpoint.memory import MemorySaver
app = graph.compile(checkpointer=MemorySaver())
# ============ 运行 ============
config = {"configurable": {"thread_id": "session_42"}}
result = app.invoke(
{
"messages": [HumanMessage(content="我的订单 #12345 还没收到,请帮查")],
"user_id": "u_abc",
"iterations": 0,
"total_cost_usd": 0.0,
"started_at": time.time(),
},
config=config,
)
# 把这次会话沉淀到长期记忆
memory.add(messages=result["messages"], user_id="u_abc")
This 100-line skeleton already has the core pieces of a production agent:
| Piece | Where it is implemented |
|---|---|
| The LLM + tool loop (ReAct) | agent_node + tool_node |
| Long-term memory | memory.search / memory.add (Mem0) |
| A RAG knowledge base | the kb_search tool |
| An input guardrail | input_guardrail |
| Four Loop of Death gates | should_continue (max iterations + cost cap + timeout + a completion check) |
| HITL (critical tickets) | a forced raise inside the create_ticket tool |
| Checkpoint / pause and resume | MemorySaver |
| Observability | LangfuseCallback |
| Cost tracking | a running total in total_cost_usd |
What you still add before this is production: automated eval (DeepEval / RAGAS), an A/B test, a staged rollout, a PagerDuty alert hookup, compliance logs, and an output guardrail for sensitive terms.
| Resource | Kind | How to use it |
|---|---|---|
microsoft/ai-agents-for-beginners |
A 12-lesson introduction | A complete start for newcomers. Each lesson has runnable code samples, paired with the Microsoft Agent Framework |
microsoft/agent-framework |
The official framework | The successor to AutoGen. Python + .NET, for enterprise agent orchestration |
datawhalechina/hello-agents |
A systematic Chinese tutorial (42K ⭐) | From principles, to implementation, to agentic RL. The most systematic treatment in the Chinese-language community |
anthropic/Building-Effective-Agents |
Anthropic’s official essay | The original source of the six patterns in §3.4. Required reading |
jcran/effective-agents-langchain |
A LangChain implementation | The LangChain code version of Anthropic’s six patterns |
langchain-ai/langgraph |
Official LangGraph | The mainstream production agent framework. The documentation is very thorough |
crewAIInc/crewAI |
Official CrewAI | A role DSL. The fastest to get running |
openai/openai-agents-python |
The OpenAI Agents SDK | Official OpenAI. The handoff philosophy |
huggingface/smolagents |
Smolagents | A few hundred lines of core code. The most minimal implementation |
mastra-ai/mastra |
Mastra | TypeScript-native. The first choice for a JS/TS project |
philschmid/ai-agent-benchmark-compendium |
A compendium of 50+ benchmarks | When you are looking for a test of one specific capability |
vectara/awesome-agent-failures |
A collection of failure modes | The negative examples to read before you write a production agent |
Papers to read (in time order):
Concepts marked
[term]in the answers jump to the matching entry in this table and expand it. The table groups 60+ high-frequency terms under 10 topics and is collapsed by default — expand a term only when you don’t know it, and read the short explanation, so you are not buried in information.
Analyze source without actually running it, and flag potential bugs, unused variables, style violations, and suspicious usage. Common tools:
In agent engineering, lint is often used as a hard constraint in the harness — feeding lint errors back to the model to rewrite the code is much more reliable than merely saying “please follow the coding standards” in the prompt (Augment Code calls this a Constraint Harness).
Make sure types are consistent across assignments and function calls:
For an agent: after the agent finishes writing code, force a run of tsc --noEmit / mypy . / cargo check, and feed type errors back to the model to keep fixing. That is much more reliable than letting the agent “claim it checked the types” — a typical practice of harness engineering.
CI (Continuous Integration): on every commit, automatically run tests, builds, lint, type check, and other checks. CD (Continuous Deployment): after the checks pass, automatically deploy to test / staging / production.
Common tools: GitHub Actions, GitLab CI, Jenkins, CircleCI, Buildkite.
In agent engineering, CI is the most important deterministic feedback source — an LLM’s self-assessment is unreliable, but CI pass/fail is a 0/1 signal and can be used directly as ground truth. Hashimoto’s harness engineering stresses that whether a problem gets solved is strongly tied to whether the agent can verify its own work.
Run untrusted code (user uploads, LLM-generated code) in a restricted environment so it cannot damage the host. Isolation strength, from weak to strong:
An agent that runs user- or LLM-generated code must be sandboxed, with defense in depth on top: a network-egress allowlist + a read-only filesystem + time/memory quotas + no host credentials mounted.
The name comes from using canaries in coal mines to detect poison gas. A typical deploy sequence is: shadow (dual-run, do not return the result) → 1% → 5% → 25% → 50% → 100%, and each stage advances only if the core metrics show no regression.
For LLM applications, a new prompt / new model / new agent harness must go out through a canary. If any key metric (resolution rate, CSAT, refusal rate, p99 latency, cost per request) worsens past a threshold, roll back. The non-determinism of LLM applications makes canaries more important than they are for traditional software.
Hash on user_id to split traffic so a given user always lands in the same bucket (avoiding a flickering experience). What to judge:
Prompt changes, model swaps, and RAG changes in an LLM application should all go through an A/B test. Shipping on offline eval alone often fails in production (the live traffic distribution differs a lot from the gold set).
The metrics the business side watches most closely when deciding whether an agent should launch:
What agent engineering watches: put model / prompt / RAG changes into an A/B test and look at these business metrics, not only offline benchmark scores. It is very common for a change to raise the benchmark while GMV / CSAT fall.
Pause an autonomous agent at a critical node and wait for a human:
Design principle: “Humans steer. Agents execute.” (OpenAI Harness Engineering). An agent does not replace a person; it amplifies a person’s judgment — the person sets the plan, sets the constraints, and reviews the result, and the agent does the execution.
Four terms defined by Google’s SRE practice:
1 - SLO. An SLO of 99.9% means 43.2 minutes of “budget” in 30 days during which the target can be missed. Once the budget is spent, non-essential releases are frozen.How to use this for an agent application:
Rule of thumb: a stricter SLO is not automatically better — 99.99% costs 5–10× more than 99.9%, and it is likely past the threshold users can perceive. Google recommends the level that is just high enough that users don’t complain.
It expires automatically. Common uses:
A rule-of-thumb conversion: 1000 English words ≈ 1300 tokens; 1000 Chinese characters ≈ 1500–2000 tokens.
Why it matters: the context window, API billing, and inference speed are all counted in tokens. Controlling the token count = controlling cost and latency. Anthropic, OpenAI, and Google each have their own tokenizer, and a ±20% difference in token count for the same text is common.
Text → a neural network → a vector of 768 / 1024 / 1536 / 3072 floating-point numbers. Vectors for synonyms or same-topic text sit close together in that space (high cosine similarity).
Main uses:
Mainstream models in 2026: OpenAI text-embedding-3-large, Cohere v4, Voyage-3, BGE-M3 (open-source SOTA; Chinese–English bilingual, combining dense / sparse / multi-vector).
It includes the system prompt, conversation history, user input, and model output; all of them count. Mainstream models in 2026:
Note: the advertised window ≠ the effective window. The RULER benchmark shows that most models’ performance drops sharply beyond 1/4 of the window (Lost in the Middle). Engineering guidance sets the threshold for “what should go into context” at 8–32k; anything longer goes through RAG or hierarchical summarization.
The two phases have different bottlenecks, so the tuning strategies differ too — prefill is optimized with parallelism / FlashAttention, and decode is optimized with continuous batching / speculative decoding. Because the two phases have opposite characteristics, engineering also uses two kinds of scheduling optimization: PD disaggregation (Prefill/Decode Disaggregation, handling the phases on different GPU pools, as in DistServe/Mooncake) and chunked prefill (split a long prefill into chunks and interleave them with decode). See A11 for detail.
Generating token t needs the K/V of every earlier token in the attention computation. Caching them so they are not recomputed every time is the most basic optimization in LLM inference.
Memory use: 2 × num_layers × num_heads × head_dim × seq_len × batch × bytes_per_elem. On a 70B model, the KV cache for a 128k context can reach tens of GB, far more than the model weights themselves.
Further optimizations:
How GPT-style decoder-only models generate: P(x_t | x_1, ..., x_{t-1}). Every generated token runs a full forward pass (even though the KV cache can be reused), so output length directly determines latency.
By comparison: BERT is non-autoregressive (masked LM: it predicts every masked position at once), and diffusion models are also non-autoregressive (they denoise the whole span in one shot).
The cost of autoregression produced a large family of speedups: speculative decoding, parallel decoding, Medusa, EAGLE, and others.
The last decoder layer outputs a logit vector of dimension [vocab_size]; after softmax it is the probability distribution of the next token.
Places engineering can get the logits (or top-k logprobs):
Note: commercial OpenAI / Anthropic APIs return only the sampled result by default; you have to turn the logprobs option on explicitly.
P(token) ∝ exp(logit / T):
Turn it up when: Self-Consistency needs diverse paths, creative writing, brainstorming. Turn it down when: code generation, math reasoning, strict formatted output, T=0.1–0.3.
Trap: T=0 is not fully identical to deterministic — non-determinism of floating-point GPU kernels plus concurrent scheduling can still leave tiny differences for the same prompt.
Xiao et al. 2023 found that during long-sequence inference, later tokens often assign a disproportionate share of attention to the first few tokens of the sequence (even when those tokens are semantically unrelated). Those tokens become a “sink” and absorb the normalization mass that softmax is required to distribute.
Why it matters in engineering:
LayerNorm: y = (x - μ) / σ * g + b, mean and variance taken along the hidden dimension. Independent of the batch, so it naturally supports variable-length sequences and single-example inference — which is why Transformers use LayerNorm rather than BatchNorm.
RMSNorm (Zhang & Sennrich 2019): y = x / sqrt(mean(x²)+ε) * g, dropping the mean subtraction and the bias. Modern LLMs such as Llama, PaLM, and Mistral have almost all switched to RMSNorm, saving 7–64% of normalization time without hurting quality.
Pre-LN vs Post-LN: doing LN before the residual (Pre-LN) trains more stably than doing it after the residual (Post-LN), and warmup can be dropped. From GPT-2 onward, Pre-LN is nearly universal.
Su et al. 2021. Represent position m as a rotation matrix R_m applied to Q/K, so that <R_m·q, R_n·k> = f(q, k, m-n) — the attention score depends only on relative distance.
Advantages: (1) no extra parameters; (2) relative position is encoded naturally; (3) compatible with linear attention. It has become the position-encoding scheme of the great majority of modern LLMs, including Llama, Qwen, and Mistral.
The extrapolation problem: naive extrapolation from 2k to 32k collapses — the rotation angles of high-frequency components fall into regions never seen in training. Mitigations:
Naive extrapolation of RoPE collapses — the rotation angles of high-frequency components fall into regions never seen outside the training context. Two engineering schemes:
Where they are used:
Numbers from practice: YaRN is nearly lossless at a 4× context extension (2k→8k or 32k→128k); beyond 8× there is still a performance drop. Extreme long context (1M+) usually needs retraining rather than YaRN alone.
Standard multi-head attention: each of the H heads has its own Q/K/V.
The gain: the KV cache shrinks to 1/8 ~ 1/H, inference throughput rises substantially, and quality is almost unchanged. Llama-2 70B, Llama-3, Mistral, Qwen2, and others use GQA.
Replace the traditional dense FFN with N parallel expert FFNs plus a router. Each token picks the top-k experts through the router (k is usually 1–2) and only those experts are activated for the compute.
Advantage: a large parameter count with little compute. Mixtral 8x7B has 47B total parameters and only 13B activated; DeepSeek-V3 has 671B total parameters and only 37B activated. At the same inference cost, capacity far exceeds a dense model.
Challenges: (1) load balancing during training (so one or two experts are not overused); (2) memory is counted by total parameters, not activated parameters; (3) communication cost is high (expert parallelism).
Single-head attention produces one attention distribution for each pair of tokens — one “relational viewpoint”. Multiple heads (h=8/32/64/128) let the model learn several relations at once: syntactic heads attend to short-range dependencies, and semantic heads attend to coreference and long-range entities.
Why a single head with a larger dimension does not work: multi-head is a mixture of h low-rank attentions and is strictly more expressive; a single head is one high-rank attention and has fewer degrees of freedom.
A further variant: GQA / MQA drops heads on the KV side to save memory, while the Q side stays multi-head.
Dao 2022. Standard attention uses O(N²) memory (it stores an N×N attention matrix), which bottlenecks long sequences. FlashAttention loads Q/K/V in blocks into GPU SRAM for the compute and does not write out the full attention matrix. Memory drops to O(N), and it is also faster — attention is memory-bound, so reading less HBM makes it faster.
The line of work:
Kwon et al. 2023. It addresses three kinds of fragmentation in the traditional inference KV cache:
Mechanism: cut the KV cache into fixed-size blocks (for example 16 tokens), and each sequence keeps a block table (a logical→physical mapping). Memory utilization rises from ~20% to >96%; multiple samples of the same prompt share prefix blocks; together with continuous batching, throughput is 2–4× HuggingFace TGI and 24× FasterTransformer.
The de facto standard open-source inference server since 2023. Core features:
Compared with: TGI (HuggingFace), TensorRT-LLM (NVIDIA; proprietary and faster), SGLang (more thorough prefix reuse), LMDeploy (from OpenCompass).
Also called in-flight batching, proposed by Yu et al., ORCA, 2022. Compared with static batching (wait until the batch is full and then run together; when short and long requests are mixed, GPU utilization is often <30%), continuous batching:
vLLM’s numbers: throughput is 5–23× static batching. It is now the default in every modern inference engine.
Leviathan et al. 2023. The procedure:
Mathematically this guarantees the output distribution matches the original model exactly (rejection sampling).
When it pays off: (1) the draft and target distributions are similar (acceptance rate >60%); (2) the large model is memory-bandwidth bound, so parallel verification does not add latency. Acceptance rate is 70–90% for code generation and 30–50% for open-ended generation. Variants: EAGLE-2/3, Medusa, Llama 3.1 self-speculative.
The KV cache is reuse within a single request; prefix caching is reuse across requests. When many requests share a system prompt / few-shot examples / agent history, compute that KV once, cache it, and later requests hit the cache and skip prefill.
Effect: in agent / RAG / multi-turn settings, the system prompt is usually 1–4k tokens and shared by almost every request, so TTFT can drop 10–100×.
Implementations: vLLM --enable-prefix-caching, SGLang RadixAttention (a radix tree that automatically manages every historical prefix), the Anthropic prompt caching API (5-minute TTL; the cached portion is priced at 0.1×).
Driven mainly by prefill (processing every input token). One of the core metrics for billing and monitoring.
Ways to lower TTFT:
Companion metric: TPOT (time per output token, the gap between tokens, also called TBT/ITL) — determined by decode.
The other core latency metric, paired with TTFT: TTFT measures “how long until the first character appears” (determined by prefill), and TPOT measures “how fast each following character appears” (determined by decode). Perceived speed of streaming ≈ 1/TPOT (tokens/s).
Mixed-precision training: weights and gradients in BF16/FP16, optimizer state still in FP32.
At deployment, quantize weights to low-bit integers and dequantize back to floating point for inference.
INT8 / INT4 quantization usually needs a calibration set (128–512 samples); AWQ is smarter (it protects the 1% salient channels). For extreme memory budgets, use GGUF (the llama.cpp format) plus 3-bit / 2-bit quantization to run on phones.
Lin et al. 2023. The core observation: protecting only ~1% of the “important weight channels” (identified by activation magnitude) is enough to hold accuracy. Scale those channels up per channel, then quantize.
Compared with GPTQ: simpler to implement, lower perplexity on Llama-2, and now the default in vLLM / TensorRT-LLM.
Frantar 2022. Layer-wise minimization of quantization error, inspired by OBQ, with a small calibration set (128 samples). Pros: early, and the ecosystem is complete (exllama / vllm / TGI all support it). Cons: sensitive to the activation distribution; important channels can be crushed.
Deployment choices in 2026: H100/H200 → FP8; A100/3090 → AWQ INT4; phones/edge → INT4 + GGUF.
The most basic form of instruction tuning: treat (input, desired output) pairs as supervised samples and use cross-entropy loss to teach instruction following.
Data volumes from practice:
The LIMA paper: 1000 curated samples can bring a 65B base close to GPT-4 conversation quality — quality >> quantity.
Hu et al. 2021. Factor the weight update into two low-rank matrices ΔW = BA (r=8/16/64) and train only those matrices, cutting trainable parameters by 100–10000×.
Memory:
Advantages: few trainable parameters / multiple adapters can be hot-swapped / mitigates catastrophic forgetting.
Dettmers et al. 2023. Three key pieces:
QLoRA for a 65B model is about 33GB and fits on one A100-48G or two RTX 3090s. Cost: training is 30–40% slower (dequantization overhead).
Three steps: (1) SFT trains a base policy; (2) humans label (chosen, rejected) preference pairs to train a Reward Model; (3) the RM scores policy outputs, and algorithms such as PPO/RLOO/REINFORCE update the policy.
InstructGPT and ChatGPT brought RLHF into the mainstream. Three major pain points:
This produced simplifications and improvements such as DPO, GRPO, and RLVR.
Trained in step two of RLHF. Input: (prompt, response). Output: a scalar reward. The training loss is Bradley-Terry: make the RM score chosen higher than rejected on (chosen, rejected) pairs.
Common weaknesses:
Alternatives: DPO removes the RM entirely, and RLVR uses a programmatically judged reward.
Rafailov et al. 2023. It proves that under a Bradley-Terry preference model plus a KL constraint, the optimal policy has a closed form, so preference pairs (chosen, rejected) can be used directly as a supervised loss:
L_DPO = -log σ(β(log π(y_w)/π_ref(y_w) - log π(y_l)/π_ref(y_l)))
Advantages: (1) no RM / no online sampling / no critic; only the policy and a reference model; (2) uses the same training stack as SFT; (3) hyperparameters are stable.
Disadvantages: depends heavily on preference-data quality; easy to overfit surface patterns of the chosen answers; high reward-hacking risk.
The main post-training path for Llama-3 and Qwen2 is DPO or a variant (IPO, KTO, SimPO).
Schulman 2017. The actor learns the policy, the critic learns a value function to estimate advantage, and clipping limits how far the policy can move each step so it doesn’t collapse.
RLHF must load four models at once — actor / ref / critic / reward — so memory is ≥4× a single model. Hyperparameters are sensitive (KL coefficient, clip ratio, advantage normalization), and training is unstable.
This produced simplifications such as DPO (drop RL), GRPO (drop the critic), and RLOO (use mean reward as the baseline).
Proposed in DeepSeek-Math 2024 and validated by R1. The core change: drop the critic (the value network). Sample G responses for the same prompt (typically G=64), normalize advantage by the within-group mean and standard deviation of the reward: A_i = (r_i - mean(r)) / std(r), then apply a PPO-like clipped objective plus KL regularization.
Effects:
On tasks that can be checked automatically — math, code, formal logic — use a program as the reward: answer match, unit tests passing, a theorem prover verifying.
Advantages:
DeepSeek-R1 and DAPO (ByteDance’s open-source SOTA) are both in this family. Limitation: it applies only to verifiable tasks; open-ended dialogue still needs RLHF / DPO.
Mechanism: parameters are pulled by the new task’s loss, and directions that encoded general knowledge get overwritten.
A mitigation mix (recommended in production):
Advanced: EWC (Fisher-information-weighted L2 regularization), model souping (average the fine-tuned weights with the original model), and extending MoE routing.
Wei et al. 2022. The simplest form: “Let’s think step by step” (Kojima 2022 zero-shot CoT).
Why it works: (1) more effective compute (more forward steps); (2) it turns “System 1” intuitive answers into “System 2” step-by-step reasoning; (3) it exposes faulty reasoning so self-consistency voting can correct it.
It is an emergent ability and is almost useless on models under 10B. Long CoT (trained with RL in R1 / o1, including backtracking and self-verification, thousands of tokens long) is the new paradigm of 2025.
Yao et al. 2023. CoT is a single linear chain and cannot go back once it is wrong. ToT generates several candidate thoughts at each step, the LLM self-evaluates state value (promising / sure / impossible), and BFS/DFS expansion can backtrack and prune.
On Game of 24, GPT-4 CoT scores 4% vs ToT 74%. Cost: LLM calls rise from 1 to 50–200.
Fits: solution spaces that need explicit search (puzzles, planning, theorem proving) where intermediate states can be scored. Does not fit: chitchat, summarization, translation.
Yao et al. 2022. The structure is a Thought → Action → Observation loop: the model first writes “Thought: I need to look up X”, then outputs “Action: search[X]”, the tool result is fed back into the prompt as “Observation: …”, and the next Thought follows.
Three effects:
It is the conceptual prototype of modern agent frameworks (Claude tool use, OpenAI function calling, LangChain, AutoGPT).
Shinn et al. 2023. After a task fails (an external verifier gives a binary reward), the agent uses an LLM to summarize the cause as a natural-language lesson, stores it in episodic memory, and adds that lesson to the prompt on the next task to change behavior.
vs Self-Refine:
On HumanEval, GPT-4 with Reflexion scores 91%, above the GPT-4 baseline of 80%.
Madaan et al. 2023. Loop N rounds inside one trajectory, with no external reward and no cross-task memory. Average gain of 20% across 7 tasks.
Trap: a weak model’s self-critique is inaccurate and revisions can get worse. Production often combines Self-Refine (within an episode) with Reflexion (across episodes).
Wang et al. 2022. With temperature>0, sample N CoT paths and vote on the final answer. On GSM8K this is about 17 percentage points above greedy CoT (PaLM 55%→74%).
Pays off when: (1) the answer space is discrete (numbers, multiple choice); (2) the model’s error rate is <50%; (3) reasoning paths are diverse (temperature 0.7–1.0). Cost: N× (typically N=5–40). It does not apply to open-ended generation.
When to use few-shot: (1) the task format is novel or the output structure is complex (a JSON schema, a specific markdown layout); (2) a classification task has many labels.
When to use zero-shot: modern models are already saturated on general tasks (summarization, translation, QA), and adding examples can introduce bias.
Traps: example order matters a lot (recency bias); accidental regularities get learned; too many examples run into Lost in the Middle. Keep it to ≤8.
The standard pipeline = Ingestion + Retrieval + Generation:
Every stage has a bottleneck: chunk size, embedding choice, insufficient recall, context order, and the model not staying faithful to the context. Production RAG ≠ “embedding + a vector store + an LLM” — it is an engineering system.
Strategies:
Okapi BM25. Relevance of a document to a query is Σ IDF(qi) · f(qi,d) · (k+1) / (f + k·(1-b+b·|d|/avgdl)). Strengths: exact term hits, high weight on rare terms, proper nouns, numbers. Weakness: synonyms don’t match (“医生” vs “doctor”).
It complements dense embeddings — hybrid search fuses the two, and recall is 5–15 percentage points above dense alone.
Dense embeddings are good at semantic similarity; BM25 is good at exact term match; the two complement each other.
Fusion methods:
score(d) = Σ 1/(k + rank_i(d)), k=60. The production default; the two score streams do not need to be normalizedα·dense + (1-α)·bm25. You have to tune α and normalize the scoresAnthropic’s Contextual Retrieval experiment: embedding alone → + contextual embedding → + BM25 → + rerank, with failure rates 5.7% → 4.0% → 3.7% → 1.9%.
Bi-encoder (embedding): encode the query and the doc separately, then compare by cosine. Fast, but there is no interaction. Cross-encoder (reranker): concatenate query + doc and feed them to a transformer; self-attention at every layer lets them interact. Relevance modeling is 20–40% stronger (NDCG@10), and it is 100× slower.
The production standard: bi-encoder recalls top-100 → cross-encoder reranks to top-10 → feed the LLM.
Mainstream: Cohere Rerank v3/v4, BGE-reranker-v2-m3 (open-source SOTA for Chinese and English), Jina Reranker v2, Mixedbread.
Trap: if the relevant document is not in the recalled top-100, reranking cannot save it — a reranker improves what was already recalled and cannot stand in for recall.
A 2026 comparison of mainstream embedding models:
Selection framework:
Key traps:
Mainstream:
ANN algorithms: HNSW (the most common), IVF, ScaNN (Google). Index time vs retrieval accuracy vs memory is a trade-off.
Naive RAG struggles with cross-document, cross-entity reasoning, unstable recall quality, and multi-hop queries. Four common advanced schemes:
Practical choice:
Es et al. 2023. The four metrics:
Production thresholds: all four ≥0.7 counts as passing; if Faithfulness < 0.7, fix that first; if Context Recall < 0.7, tune the retriever.
Named by Mitchell Hashimoto in 2026: “anytime you find an agent makes a mistake, take the time to engineer a solution such that the agent never makes that mistake again.”
This is not model fine-tuning. It is an engineered environment that makes it physically impossible for the model to fail in the same way again.
Two laws:
OpenAI’s formula: Agent = Model + Harness; “Humans steer. Agents execute.”
A comparison of mainstream frameworks, 2024–2026:
Practice: prototype by calling the SDK directly; consider LangGraph once workflow patterns (routing / parallelization / orchestrator-workers) have settled; consider LlamaIndex for pure RAG; don’t start with the LangChain “full suite”.
Released in 2024-11, and moved to an independent Steering Committee in 2025-Q4 (Anthropic / Block / OpenAI / Microsoft). Three core primitives:
It solves the N×M problem of “writing a new adapter for every new tool”. The single fastest-growing skill in 2026, 97M+ monthly downloads, and already an explicit requirement on the Anthropic Applied AI Engineer JD.
The model outputs {"name": "search", "arguments": {"query": "..."}} JSON; the runtime parses it, actually runs the function, and feeds the result back to the model. Anthropic’s Tool Use API and OpenAI function calling are the same idea.
Design principles (Anthropic’s measurements: “agent performance is proportional to how clear the tool descriptions are”):
Anthropic released Computer Use in 2024-Q4. The loop: screenshot → the LLM looks at the image and reasons → it outputs mouse and keyboard actions (click X,Y / type text / scroll) → the system executes them → screenshot again.
Browser Use (an open-source framework) is similar but limited to the browser, based on Playwright plus DOM augmentation (overlay numbered labels on clickable elements in the screenshot).
Useful for: automating sites with no API, automating desktop software, end-to-end e2e tests. Limits: slow, expensive, weak on security, and poor support for canvas-rendered apps. Production advice: if an API exists, use the API.
The main agent hands “read 30 files and research them” to a sub-agent. The sub-agent has its own context window and returns only the final summary to the main agent — the main context is not polluted by intermediate artifacts.
Claude Code’s Task tool, OpenAI Swarm, CrewAI, and others support this. It is one of the key ways to prevent a Loop of Death and a context explosion.
The agent reads it automatically when it enters the project. It usually contains:
Hashimoto’s view: each line of AGENTS.md ≈ one mistake sealed off. It is the implicit-prompt form of harness engineering.
The permission model Claude Code uses. Bash commands are authorized at verb + path granularity:
git status allowgit push --force deny (destructive)rm -rf pops a user confirmationcat ~/.ssh/* deny (sensitive path)The matching security principle: if any one layer fails, the others still catch it. Combine it with a sandbox, a network-egress allowlist, and a read-only filesystem for defense in depth.
A core design from Anthropic’s Effective Harnesses: a long-running agent will inevitably blow the context window, and LLM context alone cannot remember “which step I am on”.
The fix:
The next session runs the 5-step startup protocol and resumes from the breakpoint. Core insight: the context window is “working memory”; the filesystem is “long-term memory”.
Common patterns:
Defense in depth:
Example: a user jokes “I’m a billionaire”, it is stored in semantic memory, and the agent reasons from that assumption from then on.
Defenses:
“Full-trace plus evaluation” tools for LLM apps and agents. Mainstream in 2026:
Capabilities to line up:
How to choose: prefer Langfuse if you self-host; LangSmith for heavy LangChain users; Phoenix for trace monitoring alone; Patronus for safety / real-time blocking.
Grouped by the capability being measured:
In practice: from a 2026 point of view, no single benchmark is trustworthy anymore — you need three layers: cross-checking several benchmarks, your own gold set, and blind human votes on Arena. See E11 and H3.
Eval for an LLM or agent application is not a single action; it is a system. Common layers:
How the eval tool stack splits:
Eval types by how they score:
Rules of thumb: (1) changing a prompt with no eval is changing it blind; (2) the eval model must be ≥ the model being evaluated (the judge principle); (3) the gold set must be versioned and private; (4) every bad case must be regressed into the gold set — the harness-engineering closed loop.
Model “evaluation” as an LLM task: the judge model reads (prompt, response) and outputs a 0–1 score / pass/fail / which side wins pairwise. It parallelizes at large scale, is cheap, and can be automated in CI.
Three major biases:
The judge principle: the judge model’s capability should be ≥ the model being evaluated, or it cannot tell what is good about a stronger model’s answer. The production standard is three layers: your own gold set + an LLM judge + human calibration.
The Liu et al. 2023 experiment: put the gold passage at the start or the end of the prompt and accuracy is high; put it in the middle and accuracy falls in a U-shape. With 10+ documents, the middle position can score below a no-context baseline.
Engineering mitigations:
It is #1 on the OWASP LLM Top 10. Types:
Simon Willison’s conclusion: prompt injection has no 100% solution, and the system must be designed on the assumption that “the LLM is untrusted”. The dual-LLM pattern = a Planner (sees trusted input and decides) + an Executor (sees untrusted input but has no tool permissions).
Sources:
Debug flow: reproduce it and quantify the trigger rate → attribute the cause (RAG recall / whether the model used the context / tool calls) → fix → add a regression → production monitoring (a faithfulness scorer blocks in real time).
Key point: most hallucinations are defects in the prompt / RAG / context engineering, not “the model is sick”.
NVIDIA NeMo Guardrails defines 5 kinds:
Key point: more rails are not automatically better. Turn on 1–2 high-ROI rails per kind, or latency and cost explode.
Common techniques:
Defense: the vendor’s RLHF safety training + application-layer input/output classifiers (Llama Guard) + a cap on max context (against many-shot) + regular red-team exercises.