Agents#
An agent is a loop: the model proposes a tool call, the application executes it, the result feeds back into the model, and the loop runs until the model emits a final answer or hits a budget. The loop is the primitive; everything else (planners, multi-agent choreography, “frameworks”) is sugar on top.
The basic loop#
flowchart TD
U[user prompt] --> R["model.respond(messages, tools)"]
R --> Q{tool_calls?}
Q -->|yes| T[run tools]
T --> R
Q -->|no| A[return final answer]
Concretely, in a vendor SDK.
tools = [{"name": "search_logs", "input_schema": {...},
"description": "Search SIEM logs by query."}]
messages = [{"role": "user", "content": user_query}]
for _ in range(MAX_TURNS):
resp = client.messages.create(
model=MODEL, tools=tools, messages=messages, ...)
if resp.stop_reason == "end_turn":
return resp.content[-1].text
for block in resp.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": block.id,
"content": result}]})
Inside a model call#
The model.respond arrow in the loop above is not atomic. Each
call is the model running its decoder over the entire prompt, token
by token, until it emits a stop token or hits the configured
max_tokens budget. Five stages run in order on every call, with
the transformer’s KV cache and the sampler forming a tight inner
loop.
flowchart LR
P[prompt + tools + history] --> T[tokenize]
T --> E[embed]
E --> L[transformer layers]
L --> S[sampler]
S --> D[detokenize]
D --> O[text + tool_use blocks]
L -. KV cache .-> L
S -. next token .-> L
Tokenizer#
Bytes to integer IDs using the model’s tokeniser. Frontier models
use byte-pair encoding variants (Anthropic’s tokeniser,
tiktoken for OpenAI, SentencePiece for Gemini and Llama). The
system prompt, tool schemas, and tool results count against the
token budget exactly the same way user text does.
Concept |
Detail |
|---|---|
Vocabulary size |
100k to 200k tokens on current frontier models. Larger vocabularies cover Unicode and code better, at the cost of a larger embedding matrix. |
Special tokens |
Role markers, end-of-turn markers, tool-call delimiters. Vendor-specific and usually hidden by the SDK. |
Byte fallback |
Unknown bytes split into raw byte tokens. Pathological for binary blobs (one token per byte). Strip or base64-encode binary before sending. |
Cost asymmetry |
Output tokens are 4x to 5x the price of input tokens at most vendors. Prompts can be long; completions should not be. |
Embedding#
Each token ID maps to a dense vector via the embedding matrix. Positional information is folded in via rotary position embeddings (RoPE) applied inside attention on modern decoders, rather than added to the embedding itself. The embedding weights are typically tied to the output projection, so the same matrix is read at the top of the stack and written at the bottom.
Transformer layers#
A stack of decoder blocks, each containing self-attention followed by a feed-forward sublayer, with residual connections and layer normalization around both. The stack is causal (each token attends only to earlier tokens), which is why streaming output is possible.
Component |
Detail |
|---|---|
Self-attention |
Multi-head attention. Frontier models often use grouped-query attention (GQA) so several query heads share one key/value head, cutting KV cache size without losing much quality. |
KV cache |
Past keys and values are stored per layer per head. On each new output token only the new row is computed and appended; everything earlier is read. The KV cache is the structural reason vendor-side prompt caching exists. |
Feed-forward |
Two linear layers with a non-linearity (SwiGLU or GeGLU on frontier models). In mixture-of-experts (MoE) models a router picks a subset of expert FFNs per token, so active parameters per forward pass are a fraction of the total. |
Depth |
Typically 60 to 120 layers on frontier-scale models. Latency per output token scales roughly linearly with depth and with the active parameter count. |
Sampler#
The transformer emits logits, a probability distribution over the vocabulary for the next token. The sampler turns logits into a single token, which is fed back into the inner loop for the next forward pass.
Knob |
Effect |
|---|---|
|
Scales logits before softmax. |
|
Nucleus sampling. Sample only from the smallest set of tokens whose probabilities sum to |
|
Sample only from the top |
Structured output / tool use |
The sampler is constrained to a grammar or JSON schema; logits for tokens that would break the schema are masked out. This is how tool-call JSON stays parseable even at non-zero temperature. |
Stop conditions |
End-of-turn token, end-of-text, configured |
Detokenizer#
Output IDs reassembled into text. Structured content blocks
(text, tool_use, thinking) ride the same token stream
with special delimiters; the SDK splits the stream into typed
blocks before returning. Streaming SDKs emit partial blocks as the
delimiters land.
Two practical consequences fall out of the pipeline.
Output is sequential. Doubling
max_tokensroughly doubles latency. Input length affects time-to-first-token, not subsequent tokens.Prompt caching exploits the prefix. Stable prefixes (system prompt, tool definitions, large attached corpora) can be marked so the vendor keeps the precomputed KV cache warm for a short TTL. Cache hits drop both latency and cost dramatically.
The Claude Code harness#
Everything outside the model call is the harness. The CLI you are running owns the message list, the tool registry, the permission gate, hooks, compaction, and the on-disk trajectory. The model is treated as a stateless function.
flowchart TD
subgraph harness[Claude Code harness]
direction TB
SP[system prompt builder]
REG[tool registry]
CTX[context assembler]
PARSE[response parser]
PERM[permission gate]
DISP[tool dispatcher]
HOOK[hook runner]
COMP[compaction]
LOG[trajectory store]
end
CMD["CLAUDE.md, settings.json"] --> SP
ENV["env, cwd, git, OS"] --> SP
MCP["MCP servers, skills"] --> REG
USR[operator input] --> CTX
SP --> CTX
REG --> CTX
CTX --> API[(Anthropic API)]
API --> PARSE
PARSE -->|text| TERM[terminal]
PARSE -->|tool_use| PERM
PERM --> DISP
DISP --> HOOK
HOOK --> CTX
CTX --> COMP
CTX --> LOG
System prompt builder#
The static prompt is assembled once at session start and patched on certain events. It sits at the head of every request and is the biggest single contributor to the cacheable prefix.
Layer |
Source |
|---|---|
Identity and policy |
Compiled into the CLI binary. Defines what Claude Code is, the refusal policy, and the output-style rules. |
Environment block |
Working directory, OS version, shell, |
Tool list |
Names and JSON schemas of every tool currently resident. Deferred tools appear by name only; their schemas are pulled in on demand via |
Skills list |
User-invocable skills and their trigger descriptions. Skills are activated by name with the |
Project rules |
|
Memory index |
The contents of |
Tool registry#
The registry holds the schemas the model sees. Each entry is a JSON
description with name, description, and input_schema.
Mechanism |
Detail |
|---|---|
Built-in tools |
|
MCP server tools |
Tools exposed by Model Context Protocol servers configured in |
Deferred tools |
Tools whose schemas are too large to keep resident. Only the name appears in the prompt; the model loads the schema via |
Schema validation |
Every tool call is validated against the input schema before execution. Invalid calls return an |
Parallel tool calls |
The model may emit several |
Context assembler#
Maintains the running messages list submitted on every call. A
message is a role (user or assistant) plus an ordered
list of content blocks.
Content block |
Role |
|---|---|
|
Plain prose from user or assistant. |
|
Inline image bytes or a file reference. Used for screenshots, diagrams, scanned documents. |
|
Assistant-only. The model’s request to invoke a tool, with |
|
User-only. The harness’s response to a prior |
|
Assistant-only, extended-thinking mode. Hidden reasoning preserved across turns so the cache survives. |
The harness enforces three ordering rules. The first user message
is the operator’s prompt. Each assistant message that emits
tool_use blocks must be followed by a user message containing
one tool_result per call, in the same order. thinking blocks
are preserved verbatim across turns; reordering them invalidates
the cache.
Response parser#
Splits the model’s streamed response into typed blocks. Text blocks
go to the terminal as they stream; tool_use blocks are buffered
until the message is complete, then routed to the permission gate.
stop_reason on the final response tells the harness whether to
loop or stop.
|
Meaning |
|---|---|
|
The model is done. Return control to the operator. |
|
One or more |
|
The output budget ran out. Continue with a follow-up call or surface a truncation warning. |
|
A configured stop string was hit. Rare in agent loops. |
|
Streaming paused mid-response (long-running tool, server-side think). Resume by issuing the next request. |
Permission gate#
Every tool call passes through the gate before execution. The gate
combines the current permission mode with per-tool allow / deny
rules from settings.json.
Permission mode |
Behavior |
|---|---|
|
Tools listed in |
|
Edits to files in the working directory auto-approve; shell and external tools still prompt. |
|
All tools run without prompting. High-trust contexts only, operator-opted. |
|
Read-only mode. Mutating tools refuse; the model produces a plan and exits via |
Allow / deny rules are written as Tool(pattern) strings, for
example Bash(npm test), Read(/etc/**), or
mcp__github__*. Deny rules win over allow rules, and the
PreToolUse hook can override either.
Tool dispatcher#
Executes approved tool calls and packages the result into a
tool_result content block.
Concern |
Behavior |
|---|---|
Parallel execution |
Independent |
Timeouts |
Each tool has a default timeout. |
Background jobs |
|
Output size |
Stdout and stderr are truncated past a configured byte count; the truncation marker rides along in the |
Errors |
Non-zero exit codes, raised exceptions, and validation failures all surface as |
Hook runner#
Hooks are operator-configured shell commands invoked on harness events. They receive a JSON payload on stdin and may print JSON on stdout to influence behavior. Hooks run on the operator’s machine, not on the model.
Event |
Fires |
|---|---|
|
Just before the operator’s prompt is added to the message list. Can inject text or block the prompt. |
|
After the permission gate, before execution. Can deny the call, modify input, or pass it through. |
|
After the tool returns, before the result is appended. Can append text to the result the model sees. |
|
Model emitted |
|
A subagent completed. Mirror of |
|
Idle or waiting on input. Useful for desktop notifications and pager wakeups. |
A hook that exits non-zero with {"decision": "block"} cancels
the event. Hook output text is fed back to the model as if from the
operator, which is why UserPromptSubmit hooks can inject system
reminders the model treats as authoritative.
Compaction#
When the running token count crosses the configured threshold, the harness invokes the model to rewrite older turns as a compact summary, freeing window space for the next call.
flowchart LR
T1[turn 1] --> T2[turn 2] --> T3[turn 3] --> TN[... turn N]
TN --> CHK{near limit?}
CHK -->|no| NEXT[next call]
CHK -->|yes| SUM[summarize turns 1..k]
SUM --> KEEP["system + summary + turns k+1..N"]
KEEP --> NEXT
The system prompt, tool schemas, and
CLAUDE.mdstay verbatim; they are short and live inside the cache.Recent turns stay verbatim so the model retains detail on what is currently in flight.
Older turns are replaced by a narrative summary the model writes itself. File-edit history, key decisions, and open threads are preserved; verbose tool output is dropped.
/clearis the operator’s manual compaction. It resets the message list to the system prompt only.
Trajectory store#
Every turn, tool call, and tool result is appended to a JSONL
transcript under ~/.claude/projects/<encoded-cwd>/. The store
is the audit trail, the input to /resume, and the most useful
artifact when debugging a misfire.
Path |
Contents |
|---|---|
|
The full message stream, one JSON object per turn. |
|
Persistent memory files written by the model. |
|
Index pointing at memory files; loaded into context on session start. |
|
User-scope permissions, hooks, environment variables. |
|
Project-scope overrides, checked into the repo. |
|
Project-scope, not checked in. |
Tool call lifecycle#
A single tool invocation is the smallest unit of an agent’s external effect. The harness mediates every step.
sequenceDiagram
participant O as Operator
participant H as Harness
participant M as Model
participant T as Tool
O->>H: prompt
H->>M: messages + tool schemas
M-->>H: tool_use(name, input)
H->>H: validate against schema
H->>H: permission check
alt pre-authorized
H->>T: execute(input)
else gated
H->>O: approve?
O-->>H: yes
H->>T: execute(input)
end
T-->>H: stdout / data / error
H->>M: tool_result(id, content)
M-->>H: more tool_use OR end_turn
H-->>O: render text
Tool calls are content, not control flow. The model emits a
tool_use block inside its assistant message; the harness threads
the result back as a tool_result block on the next user turn.
The model never calls anything directly.
Context window assembly#
The model is stateless. Every turn the harness rebuilds the full prompt and submits it. Layer order matters because prompt caching keys on the prefix.
flowchart TB
subgraph window[context window]
direction TB
A[system prompt + harness rules]
B[tool schemas]
C["CLAUDE.md + project rules"]
D[memory index entries]
E["older turns (compacted summary)"]
F["recent turns (verbatim)"]
G[current user message]
A --> B --> C --> D --> E --> F --> G
end
CACHE[("prompt cache, 5-min TTL")]
CACHE -. covers .-> A
CACHE -. covers .-> B
CACHE -. covers .-> C
CACHE -. covers .-> D
The first four layers are stable for most of a session and live inside the cache breakpoint. The lower layers change every turn and miss the cache by design. Short, frequent calls keep the cache warm; long sleeps (over five minutes for Anthropic) drop the warm state and the next call pays full prefix cost.
Subagents#
A subagent is a child harness with its own context window. The
parent spawns it via the Agent tool with a task description and
sees only the final result.
flowchart TD
P[parent harness]
SA["subagent A (own window)"]
SB["subagent B (own window)"]
P -->|"spawn(task A)"| SA
P -->|"spawn(task B)"| SB
SA -->|final result| P
SB -->|final result| P
Context protection. Searching a large repo produces tens of thousands of tokens of file content. A subagent reads, distils, and returns the answer; the parent’s window is untouched.
Parallelism. Independent subtasks (run linter, run tests, fetch URLs) progress concurrently. Each subagent’s trajectory is independent.
Isolation modes.
isolation: "worktree"spawns the subagent inside a fresh git worktree so its edits do not collide with the parent’s working tree.Subagent types.
Explore(read-only search),Plan(architect),general-purpose, plus user-defined agent types in.claude/agents/.
Other agent forms#
The loop primitive admits more than one form. The differences are in how the loop is structured, not in what it is.
ReAct#
Older pattern from before native tool use. The model alternates explicit “Thought” and “Action” tokens; the harness parses the substrings out of plain text. The modern equivalent is a loop with tool calls, but ReAct still applies when the model has no tool-use API or when running over a chat-completions endpoint.
flowchart LR
THK[Thought] --> ACT[Action]
ACT --> OBS[Observation]
OBS --> THK
THK --> ANS[Final Answer]
OpenAI Assistants / Responses API#
The vendor hosts the message list, the tool definitions, and the file context. The client creates a thread, attaches files, kicks off a run, and polls until the run completes or surfaces a tool call. Trades local control for a managed trajectory and built-in code interpreter, file search, and computer-use tools.
LangGraph and state-machine harnesses#
Explicit state machine over the loop. Nodes are functions, edges are conditional transitions, state is a typed dict. Useful when the workflow is not a plain loop, for example branching plans, parallel fan-out / fan-in, retries with back-off, or human-in-the-loop checkpoints with persistence between sessions.
flowchart LR
START([start]) --> PLAN[plan]
PLAN --> RUN[execute step]
RUN --> CHECK{done?}
CHECK -->|no| RUN
CHECK -->|yes| REVIEW[review]
REVIEW -->|approve| DONE([end])
REVIEW -->|revise| PLAN
Designing tools#
Tools are the agent’s surface to the world. Design them like any other API.
One job per tool.
search_logs(query, time_range)beatsquery_siem(action, params); the model picks the right tool more reliably.Strict schemas. JSON schema with
requiredfields, enums where possible, no free-formparams: object.Errors are content. Return
{"error": "...", "hint": "..."}as the tool result. The model reads it and adjusts.Idempotent where possible. The agent will retry. Make retries safe.
Authorize outside the prompt. Tool authorization lives in the application, not in the system prompt. The model proposes; the application disposes.
Operational concerns#
Budget the loop. Cap the turn count, the wall-clock time, the total tokens. Agents that are not budgeted will eventually run forever.
Persist the trajectory. Every tool call, result, and intermediate response. The trajectory is the agent’s audit trail and the only useful debugging artifact.
Compaction. Long trajectories blow past the context window. Summarize older turns; keep the system prompt and the most recent N turns verbatim.
Human-in-the-loop on irreversible actions. Email, payment,
rm -rf, branch deletes, prod deploys: gate behind a confirmation step. The model proposes; the operator confirms.Subagents for parallel or context-protected work. Each subagent gets its own context window; the parent only sees the result.
Multi-agent vs. single-agent#
Multi-agent systems (planner / executor / critic) are easy to prototype and hard to operate: more state, more failure modes, more prompt drift. Default to a single agent with good tools. Add a second agent only when the workload genuinely splits along context or trust boundaries (e.g. a reviewing agent that audits the acting agent’s tool calls).
flowchart TD
P[Planner] -->|plan| E[Executor]
E -->|result| C[Critic]
C -->|approved| OUT[final answer]
C -->|revise| P
Frameworks#
Vendor harnesses (Anthropic Agent SDK, OpenAI Agents) are the thinnest practical wrappers; they do the loop and the trajectory for you.
LangGraph / LlamaIndex Agents add explicit state machines. Useful when the workflow is not a plain loop.
Plain code beats both for anything you understand. The loop is twenty lines.
References#
SDKs for the underlying tool-use API.
Evals for evaluating agent trajectories, not just final answers.
LLM-Assisted Analysis for analyst-facing agentic patterns.