An agent is a model calling tools in a loop until a given task is complete.
Agent = Model + HarnessThe job of a harness: get the model the right context at the right time for the given task.
A harness is everything around that loop: the model, its prompt, its tools, and any middleware that shapes its behavior.create_agent is a highly configurable harness. At its simplest, you can create one with:
from langchain.agents import create_agentagent = create_agent(model="google_genai:gemini-3.5-flash", tools=tools)
Building on that, you can configure the basics directly with the model=, tools=, and system_prompt= parameters. For more advanced capabilities, extend the harness with middleware.
Pass a model identifier string ("provider:model") or an initialized model instance to select the model for your agent. See Models for parameters, provider setup, and dynamic model selection.
from langchain.agents import create_agentagent = create_agent(model="google_genai:gemini-3.5-flash", tools=tools)
To provide the agent with tools, pass any Python callable, LangChain tool, or tool dict. See Tools for tool definition, context access, and dynamic tool selection.
Trace each step of this loop, debug tool calls, and evaluate agent outputs with LangSmith. Follow the tracing quickstart to get set up. We recommend you also set up LangSmith Engine which monitors your traces, detects issues, and proposes fixes.
You can invoke an agent with a message. Behind the scenes that passes an update to the agent’s State. All agents include a sequence of messages in their state; to invoke the agent, pass a new message along with a thread_id so the agent can persist and resume conversation history:
from langchain.agents import create_agentfrom langchain_core.utils.uuid import uuid7from langgraph.checkpoint.memory import InMemorySaveragent = create_agent( model="google_genai:gemini-3.5-flash", tools=[], checkpointer=InMemorySaver(),)config = {"configurable": {"thread_id": str(uuid7())}}result = agent.invoke( {"messages": [{"role": "user", "content": "What's the weather in San Francisco?"}]}, config=config,)# A follow-up turn on the same conversation: reuse the same thread_id to keep historyresult = agent.invoke( {"messages": [{"role": "user", "content": "What about tomorrow?"}]}, config=config,)
Persisting conversation history with thread_id requires the agent to be configured with a checkpointer. When deployed on LangSmith, a checkpointer is provisioned automatically. Locally, pass one explicitly, for example create_agent(..., checkpointer=InMemorySaver()).
If you also need to pass per-run configuration (such as a user ID, API keys, or feature flags) to tools and middleware, pass it as context alongside config. Define the shape of that data with context_schema and access it through runtime.context:
from dataclasses import dataclassfrom langchain.agents import create_agentfrom langchain_core.utils.uuid import uuid7from langgraph.checkpoint.memory import InMemorySaver@dataclassclass Context: user_id: stragent = create_agent( model="google_genai:gemini-3.5-flash", tools=[], context_schema=Context, checkpointer=InMemorySaver(),)result = agent.invoke( {"messages": [{"role": "user", "content": "What's the weather in San Francisco?"}]}, config={"configurable": {"thread_id": str(uuid7())}}, context=Context(user_id="user-123"),)
thread_id scopes the conversation (message history, checkpoints), while context carries per-run data your tools and middleware read at invocation time. Both are commonly passed together. See tool context and Runtime for more.
invoke returns the final response at the end of a run. If an agent executes multiple tool calls, users often need progress updates before completion. Use streaming to surface intermediate messages and tool activity as they happen.
from langchain.messages import AIMessage, HumanMessagestream = agent.stream_events( {"messages": [{"role": "user", "content": "Search for AI news and summarize the findings"}]}, version="v3",)for snapshot in stream.values: # Each snapshot contains the full state at that point latest_message = snapshot["messages"][-1] if latest_message.content: if isinstance(latest_message, HumanMessage): print(f"User: {latest_message.content}") elif isinstance(latest_message, AIMessage): print(f"Agent: {latest_message.content}") elif latest_message.tool_calls: print(f"Calling tools: {[tc['name'] for tc in latest_message.tool_calls]}")
For streaming modes, event types, and UI patterns, see Streaming.
create_agent is highly extensible. Middleware is the primitive for customization: each piece handles one concern, hooks into the agent loop at the right moment, and composes freely with any other. Take exactly what your use case needs and skip the rest.Common patterns are prebuilt as first-class middleware. You can build anything else as custom middleware.As agents take on complex work, they need support across a few key areas. The middleware ecosystem provides:
Execution environment
Tools, filesystem, sandboxes, and code execution
Context management
Summarization, memory, skills, and prompt caching
Planning and delegation
Todo lists and subagents for parallel, isolated work
Fault tolerance
Retries, fallbacks, and call limits
Guardrails
PII detection and content controls
Steering
Human-in-the-loop approval before high-impact actions
create_deep_agent pre-assembles this stack for long-running coding and research tasks (filesystem, summarization, subagents, and prompt caching included by default). See Deep Agents for the full prebuilt harness.
Agents are especially useful when they can take action rather than just generate text. The execution environment gives the agent a workspace: tools it can call, a filesystem for reading and writing files across turns, and code execution for running scripts or shell commands.
Every model call has a fixed context window. As an agent runs, that window fills with accumulating history, tool results, and intermediate steps. Summarization compresses history before overflow hits; memory loads persistent instructions at startup so knowledge carries across sessions; skills surface domain knowledge on demand rather than loading everything upfront.
Complex tasks often exceed what one context window can handle. Delegation lets the main agent break work into pieces, hand them to subagents that each run in their own isolated context, and stay focused on coordination rather than execution. Work can run in parallel; the main agent’s context stays clean.
from deepagents.backends import StateBackendfrom deepagents.middleware import FilesystemMiddlewarefrom deepagents.middleware.subagents import SubAgentMiddlewarefrom langchain.agents import create_agentfrom langchain.agents.middleware import TodoListMiddlewarefrom langchain.tools import tool@tooldef search(query: str) -> str: """Search for a query and return a short summary.""" return f"Search results for: {query}"backend = StateBackend()agent = create_agent( model="google_genai:gemini-3.5-flash", tools=[search], middleware=[ FilesystemMiddleware(backend=backend), TodoListMiddleware(), SubAgentMiddleware( backend=backend, subagents=[ { "name": "researcher", "description": "Searches and returns a structured summary.", "system_prompt": "Use the search tool to research the question and summarize key points.", "tools": [search], "model": "anthropic:claude-sonnet-4-6", "middleware": [], } ], ), ],)
Agents in production encounter failures that rarely appear in development: rate limits, model timeouts, transient API errors. Fault tolerance middleware handles these at the infrastructure level so your tools and business logic don’t need try/catch around every call.
from langchain.agents import create_agentfrom langchain.agents.middleware import ModelRetryMiddleware, ToolRetryMiddlewarefrom langchain.tools import tool@tooldef search(query: str) -> str: """Search for a query and return a short summary.""" return f"Search results for: {query}"agent = create_agent( model="google_genai:gemini-3.5-flash", tools=[search], middleware=[ ModelRetryMiddleware(max_retries=3), ToolRetryMiddleware(max_retries=2), ],)
Some policies can’t live in a prompt—they need to be enforced deterministically regardless of what the model does. Guardrails intercept data as it flows through the agent loop, applying compliance rules or content policies before tool results reach the model’s context.
from langchain.agents import create_agentfrom langchain.agents.middleware import PIIMiddlewarefrom langchain.tools import tool@tooldef search(query: str) -> str: """Search for a query and return a short summary.""" return f"Search results for: {query}"agent = create_agent( model="google_genai:gemini-3.5-flash", tools=[search], middleware=[PIIMiddleware("email")],)
Full autonomy isn’t always appropriate. Steering lets you place humans at specific decision points—before destructive writes, expensive API calls, or anything requiring judgment—without restructuring your agent. The agent pauses and waits; a human approves, edits, or rejects; execution continues.
from langchain.agents import create_agentfrom langchain.agents.middleware import HumanInTheLoopMiddlewarefrom langchain.tools import tool@tooldef search(query: str) -> str: """Search for a query and return a short summary.""" return f"Search results for: {query}"agent = create_agent( model="google_genai:gemini-3.5-flash", tools=[search], middleware=[HumanInTheLoopMiddleware(interrupt_on={"write_file": True})],)