Whetstone.
Tools and the Agent LoopThe ReAct loop, and createAgent
Module 2, Lesson 225 min

The ReAct loop, and createAgent

The ReAct loop, from first principles: call the model; if it requested tools, execute them and append the results as tool messages; call the model again; repeat until it stops requesting tools and returns a final answer. Reason, act, observe, repeat. That loop is all “agent” means.

createAgent with structured output
import { createAgent, tool, toolStrategy } from "langchain";

const agent = createAgent({
  model,
  tools: [snapshotInbox, applyPlan],
  systemPrompt: "You triage the Todoist inbox. Emit file actions only.",
  responseFormat: toolStrategy([TriagePlanSchema]),
});
const out = await agent.invoke({ messages });
out.structuredResponse; // typed TriagePlan when responseFormat is set

responseFormat: toolStrategy([ZodSchema]) is how you get typed structured output out of the agent loop, surfaced as result.structuredResponse. That is the modern replacement for the bare withStructuredOutput call from module 1, when you are inside an agent rather than calling a model directly.

Middleware wraps the loop with cross-cutting behavior, composed in a middleware: [...] array: summarizationMiddleware compresses long histories automatically, humanInTheLoopMiddleware pauses on chosen tools for human sign-off (module 5’s territory), piiRedactionMiddleware strips PII. For this module you want the loop plus structured output; the destructive-action gate comes in module 5.

Practice

Try it yourself

Quiz

Why the rename matters

createReactAgent being superseded by createAgent is more than a name change. What actually changed?

  1. AcreateAgent moved into the langchain package and changed how you configure output and middleware
  2. BNothing, it's a pure rename
  3. CcreateAgent no longer supports tools
  4. DcreateAgent now requires a checkpointer, where createReactAgent did not
Show answer

Correct answer: A — createAgent moved into the langchain package and changed how you configure output and middleware

The move relocates the prebuilt agent into the langchain package and changes the configuration surface, notably how structured output (toolStrategy / responseFormat) and middleware (humanInTheLoopMiddleware, summarizationMiddleware) are wired in. The old export survives at @langchain/langgraph/prebuilt so existing code still runs, but it is formally @deprecated in the type declarations -- not merely absent from the docs.

Recall

toolStrategy vs withStructuredOutput

Inside the loop, tool-calling is already doing one job. The structured response would be a second use of the same mechanism.

Why does createAgent need responseFormat: toolStrategy([...]) rather than just wrapping the model in withStructuredOutput?

Reveal answer

Inside the agent loop the model is also using tool-calling to invoke your actual tools (snapshot, apply, etc). toolStrategy lets the model both call real tools during the loop and, when it's done, emit a final structured response, surfaced as result.structuredResponse, without those two tool-calling uses colliding.

Do

Build the todoist triage agent

Build a real triage agent that reads the inbox and emits a plan, file actions only, guarded against destructive ops.

  • Create the build under ~/dev/groundschool/builds/m2-todoist-agent.
  • Wrap scripts/todoist/client.py's snapshot, inbox, and apply subcommands as LangChain tools.
  • Wire createAgent with those tools and responseFormat: toolStrategy([TriagePlanSchema]) matching the {actions:[{id,do,project,labels,priority,due}]} apply contract.
  • Run it against a real snapshot already sitting in vault/research/todoist/.
  • Write a guard test asserting the agent's emitted plan never contains a close or graduate action.
Done whenThe agent's plan JSON validates against the existing apply schema, the guard test proves it never emits close/graduate, and it ran successfully against a real snapshot.
Check

Plan validates against the apply contract

Confirm the emitted plan matches the existing consumer contract.

You should see

Plan JSON validates against {actions:[{id,do,project,labels,priority,due}]}, and a guard test proves the agent never emits close or graduate actions.

Sign in to track your progress →