Day 23

Agent frameworks: LangGraph.js, Claude Agent SDK, OpenAI Agents SDK

You ship
The Day 11 research agent on 3 frameworks, measured on 5 topics, with a one-page comparison.

What you'll build today

Since Day 11, WorkBot's agents have run on a loop you wrote yourself, about 90 lines long. It asks the model, runs the tools the model calls, hands back the results, and stops within its limits. Agent frameworks offer that loop ready-made, with their own ideas about state, control flow and handoffs between agents. The question is when those extras are worth what they cost.

Today you answer it by building the same research agent 3 times, on LangGraph.js, the Claude Agent SDK and the OpenAI Agents SDK. All 3 share one set of tools and one prompt, so the only thing that changes is the framework. Along the way you learn what a graph with saved checkpoints buys you, what guardrails and handoffs are, and where a framework's overhead shows up in tokens and time.

By the end of the day you have run all 3 on the same 5 questions, measured them, and written down which one WorkBot uses and why.

By the end of the day you can

  • Build the same agent on 3 frameworks, and explain where each one earns its abstractions
  • Model an agent as an explicit graph with typed state, checkpoints and conditional edges
  • Choose a framework for a given problem, with a written tradeoff

Topics

What a Framework Adds

  • The baseline: the Day 11 loop (messages, tool registry, stop conditions). A framework is that loop plus opinions
  • Where frameworks differ: state representation, control flow, handoffs between agents, built-in tracing
  • Cost: lock-in to the framework's state shape, upgrade churn, and debugging through its abstractions
  • Benefit: checkpointing, retries, tracing and handoffs you do not write yourself
  • When to skip the framework: a single-agent tool loop under 200 lines

LangGraph.js

  • Graphs: nodes are functions over typed state, edges are static or conditional, and the graph compiles to a runnable
  • State: an annotated schema with reducers, the functions that merge each update (append messages, overwrite fields)
  • Checkpointers: persist state per thread, resume after a failure, rewind to an earlier step
  • Human-in-the-loop: interrupt before a node, inspect the state, resume with edits
  • Streaming modes: values, updates and messages, and which one a UI needs

Claude Agent SDK

  • Agent definition: instructions, tools and model, with a built-in tool loop and context management
  • Subagents and handoffs, and permissions and hooks around tool execution
  • Position: the machinery Claude Code runs on, exposed as a library

OpenAI Agents SDK (TypeScript)

  • Primitives: agents, tools, handoffs, guardrails
  • Runner: executes an agent until a final output, with a maximum number of turns
  • Tracing: built-in traces of every run, exportable to other backends
  • Sessions: conversation memory across runs

Choosing

  • Explicit graph, retries at specific steps, resumable long runs: LangGraph.js
  • Anthropic models, parity with Claude Code tooling, hooks and permissions: Claude Agent SDK
  • OpenAI models, designs heavy in handoffs, guardrails as code: OpenAI Agents SDK
  • Record the choice in the architecture document, with the 2 reasons that decided it

Build

Features shipped today

  • WorkBot: The investigation agent rebuilt on all 3 frameworks, and run on the 5 test topics

Build the Day 11 research agent 3 times, once per framework, with identical tools (web_search, read_url, save_finding, write_report) and the same system prompt:

  1. LangGraph.js: a plan node, a research loop, a write node and a checkpointer. The loop's conditional edge goes to the tools and back while the model calls them. The checkpointer lets a run resume after the process is killed mid-research
  2. Claude Agent SDK: one agent with the 4 tools, and a hook that logs and counts every tool call
  3. OpenAI Agents SDK: a Researcher agent that hands off to a Writer agent, with a guardrail that rejects topics outside an allowed list
  4. Run all 3 on the same 5 topics. Record the wall time, tokens, cost and tool calls of each run
  5. Write a one-page comparison: what each framework made easier, what it made harder, and which one your products will use

Step by step

One tool set, three loops

Define the tools and the prompt once, as plain functions, and let each framework wrap them. The frameworks then differ only in the machinery around the same agent.

Study first

  • What a framework adds to the loop you wrote on Day 11: a shape for state, control flow you can draw, and handoffs between agents. It also brings tracing and checkpoints you do not write yourself. In return, its state shape ends up in your code, its upgrades bring churn, and you debug through its abstractions.
  • The 4 tools are web_search, read_url, save_finding and write_report. When BRAVE_SEARCH_KEY is set, web_search searches the web, and otherwise it searches WorkBot's knowledge base (Day 13, under content/knowledge/). read_url reads either kind of address. The findings and the report go to a notebook, a small object the run owns. Each framework's own state reads from the notebook, so the framework's state and the tools stay in step. Anthropic's post on writing tools for agents explains why a few well-described tools beat many, and why each result should be short and readable.
  • When to skip the framework: for a single-agent tool loop the size of Day 11's, 90 lines. Day 15's analyst runs on that loop and stays as it is. Anthropic gives the same advice on when and how to use frameworks. Start with the model's API, because a framework's layers can hide the prompts and responses.

Build

  1. Write src/lib/frameworks/tools.ts. Build the 4 tools with Day 12's tool() helper, add a Notebook that holds the findings and the report, and address a document as knowledge://section/slug.
  2. Write src/lib/frameworks/researcher.ts, with the instructions, the step limit, and the result shape every framework returns. The result holds the report, the findings, the tool calls, the usage, the dollars and the milliseconds.
  3. Write the 5 questions in content/eval/topics.json, all of which the knowledge base can answer.

Done when

  • The tools' tests pass. With no key, a search finds a document and gives an address that read_url reads, and a finding and a report land in the notebook.
Technical notes
  • read_url returns a document with its title as the heading, and the model then cites that title as the source.
  • One function, costOf, prices every framework's usage with the prices per model from src/lib/providers.ts (Day 19), so the dollars column compares like with like. The Claude Agent SDK also reports its own total, which checks the arithmetic.

The agent on LangGraph.js

In LangGraph.js, the loop becomes a graph. Nodes are functions over a typed state, and edges are static or conditional. The compiled graph runs with a checkpointer, which saves the state after every node so that a killed run can continue.

Study first

  • State as an annotated schema: each field has a default and a reducer. A reducer is the function that merges a node's update into the state, such as appending messages, appending findings or adding usage. A node then returns only the part it changed.
  • Nodes and edges: plan runs once, and research runs with the tools bound. A conditional edge goes from research to the tools while the model calls them, and to write when it stops. write has only write_report bound, with tool_choice set to it.
  • Checkpoints: put a thread id in the run's config, { configurable: { thread_id } }, and the checkpointer saves the state after each node. invoke(null, config) continues from the last saved node, and getState(config) shows where a thread stopped.
  • Streaming modes, for a UI: values streams the whole state after each node, updates streams only the change, and messages streams the tokens.

Build

  1. Write src/lib/frameworks/langgraph.ts, with the state and its reducers, the 4 nodes, the conditional edge, and compile({ checkpointer }). Use PostgresSaver from @langchain/langgraph-checkpoint-postgres when DATABASE_URL is a Postgres address, and MemorySaver otherwise.
  2. Make the tools node run the shared tools against a notebook of its own, and return what they saved as state, so that a resumed run still has its findings.
  3. Write a test that scripts the model. The second research call throws, as a killed process would. getState then shows the findings saved before it, and invoke(null) finishes the run without redoing them.

Done when

  • The graph test passes.
  • pnpm frameworks --only langgraph --topic refunds writes a sourced report.
  • When you kill a run midway and start it again with the same --topic and --thread <id>, it continues from the last node the checkpointer saved, without redoing the findings.
The research graph
The compiled graph as LangGraph draws it. Plan comes first, then research, whose conditional edge goes to the tools while the model calls them and to write when it stops.
Technical notes
  • A state field and a node cannot share a name, so fold the plan into the question's message instead of keeping it in a field called plan.
  • When a conversation ends on the assistant's turn, the API reads it as a prefill, the start of an answer for the model to continue. Haiku 5.5 refuses a prefill, so the plan node ends with a user message.
  • The Postgres checkpointer creates its own tables on setup(), and in tests MemorySaver keeps the same interface.

The Claude and OpenAI SDKs

The other two frameworks keep the loop out of sight. The Claude Agent SDK is Claude Code's loop as a library. The OpenAI Agents SDK gives you agents, tools, handoffs and guardrails as primitives, and through its adapter it runs on any model the AI SDK reaches.

Study first

  • The Claude Agent SDK: your tools become an in-process Model Context Protocol (MCP) server, and the SDK owns the loop and its context management. maxTurns bounds the loop, and hooks run around every tool call. Set permissionMode: "bypassPermissions", with allowedTools naming the server's tools, and no call asks for permission. The result message carries the usage and the cost.
  • The OpenAI Agents SDK: an Agent has instructions, tools, handoffs and guardrails, and run executes until a final output or maxTurns. A handoff is a tool the model calls to pass the conversation, history included, to another agent in the same run. An input guardrail checks the first agent's input. If you set it not to run in parallel with the model, it runs before any model call, and when it trips the run throws a typed error. Tracing is built in, and goes to OpenAI's backend unless a processor sends it elsewhere.
  • Models: the OpenAI Agents SDK takes an AI SDK model through its adapter, so the whole comparison runs on one Anthropic model.

Build

  1. Write src/lib/frameworks/claude.ts. Create the server with createSdkMcpServer and the 4 tools. Call query() with the instructions as the system prompt, tools: [] so that no built-in tool is offered, allowedTools for the server, and maxTurns: 20. Add a PreToolUse hook that logs every call and counts it, in a closure the run returns.
  2. Write src/lib/frameworks/openai.ts, where a Researcher agent with search, read and save hands off to a Writer agent with write_report. Add an input guardrail that holds the research desk's remit as code. The remit, remit, is a list of 20 subjects, such as order, shipping, refund, invoice and vendor. A topic that names none of them sets tripwireTriggered, and the run throws before any model call.
  3. Write a test that the guardrail refuses an off-remit question with no model call. The live tests run each framework on one topic.

Done when

  • pnpm frameworks --only claude and --only openai each write a sourced report.
  • A question about the capital of France makes the OpenAI agent throw the guardrail's error with no model call, and the test checks it.
Technical notes
  • The Claude Agent SDK's default system prompt is minimal, not Claude Code's. A string replaces it entirely, and tools: [] keeps the file and shell tools out of a research agent.
  • The AI SDK adapter is marked beta, and needs an AI SDK provider of specification v2 or later, which @ai-sdk/anthropic 4 is. In the reference run its traces were off, because the exporter needs an OpenAI key.
  • The handoff is a tool named after the target agent. The researcher's instructions say to transfer instead of writing, or a small model writes the report itself.

The numbers and the choice

With all 3 frameworks working, run them on the same 5 topics and compare the runs. Then record the choice and the 2 reasons that decided it.

Study first

  • What to measure for each run: wall time, tool calls, tokens in and out, cost, and findings. A framework's overhead shows in the tokens, from its own prompts and tool descriptions, and in the time, from its own calls.
  • Choosing: an explicit graph, retries at specific steps and resumable long runs point to LangGraph.js. Anthropic models and parity with Claude Code's tooling, hooks and permissions point to the Claude Agent SDK. OpenAI models, designs heavy in handoffs, and guardrails as code point to the OpenAI Agents SDK. Write the choice in the architecture document, with the 2 reasons that decided it.
  • What each framework says it is for. The LangGraph overview names durable execution and human-in-the-loop as its core benefits. The Claude Agent SDK's comparison with Anthropic's other tools shows what it adds over a loop you write yourself, such as hooks, permissions and sessions.

Build

  1. Add pnpm frameworks, which runs each framework on the 5 topics and prints a row per run and the totals per framework. It saves the reports under .data/frameworks/.
  2. Write docs/frameworks.md, with the table, what each framework made easier and harder, and which one WorkBot uses.
  3. Record the decision in brain/architecture/decisions.md, the brain's numbered decisions from Day 21, and in docs/architecture.md.

Done when

  • The table has 15 rows, and the comparison names the 2 reasons for the choice.
The matrix
The same 5 questions on the 3 frameworks, with the time, tool calls, tokens, cost and findings of each run, and the totals.
Technical notes
  • The reference run made 15 runs on Haiku 5.5. A LangGraph.js run averaged 23 s, 16 tool calls and $0.005, and a Claude Agent SDK run averaged 18 s, 14 calls and $0.005. An OpenAI Agents SDK run averaged 19 s, 14 calls and $0.003. Each run made 3 to 7 findings, and every run wrote a sourced report. Time and tokens vary more from run to run than from framework to framework, because the model decides how much to read.
  • Tokens tell the frameworks apart where dollars do not. The Claude Agent SDK carries Claude Code's own context into every turn, about 15,000 tokens, which are read from the cache at a tenth of the price. So a run moves 270,000 input tokens, and costs the same as a LangGraph run's 30,000.
  • The reference run recorded the choice as decision D12 in the brain. The product keeps its 90-line loop, and the Claude Agent SDK stays on the explainer pipeline from Day 12, where parity with Claude Code's hooks and permissions matters. LangGraph.js is the framework for the first agent that must pause for a person and resume. Its checkpoints live in the Postgres the app already has, and its graph is drawable and testable with a scripted model. The OpenAI Agents SDK is a pattern to copy, not a dependency, because WorkBot runs Anthropic models and the adapter for them is beta.

Resources