Skip to content
How-to Intermediate 45 min read by Rajat Jain Updated August 13, 2026

Build Your First Autonomous AI Agent in a Weekend

The 2026 edition: a real, runnable LangGraph agent with tools, memory, and a human-in-the-loop gate - verified against the official quickstart, not pseudo-code.

Note

Verified against the official LangGraph quickstart (docs.langchain.com, fetched 13 August 2026). The scaffold mirrors the Graph API example verbatim; wire a local Ollama endpoint or any OpenAI-compatible base URL in step 1.

Before you start

  • Python 3.10+ and a terminal
  • A model endpoint - your local Ollama from our install guide works perfectly via localhost:11434/v1
  • `uv` installed (or pip - the docs use uv; both work)
Jump to section
  1. 1

    Install the stack

    `uv init && uv add langchain langchain-openai langgraph`. One command gets you the model layer, the tool layer, and the graph runtime.

  2. 2

    Define tools and model

    Decorate 2-3 real functions with @tool and bind them to the model. Keep the tool surface small - every tool is a new attack surface for prompt injection.

  3. 3

    Define state and nodes

    A TypedDict for messages (appended, never replaced) plus two nodes: the model call and the tool executor. This is the loop: model proposes, tool executes, result feeds back.

  4. 4

    Wire the conditional edge

    If the last message carries tool calls, route to the tool node; otherwise end. Compile the graph and invoke it. You now have a ReAct agent.

  5. 5

    Add memory and a human gate

    Attach a checkpointer for cross-session recall and an interrupt for any write/delete tool, so a human approves before anything destructive runs.

  6. 6

    Ship it safe

    Log every tool call with payloads, cap steps at the graph level, and run it inside Docker Sandboxes if it touches the network - never on your bare host.

A genuinely useful autonomous agent is a weekend project in 2026 - if you keep the scope tight and the loop disciplined. This guide builds the official LangGraph quickstart agent verbatim (with one swap: it runs against your local Ollama), then adds memory and a human-in-the-loop permission gate. No pseudo-code, no framework roulette: this is the canonical scaffold, documented.

The mental model

An agent is: a model, a loop, tools, memory, and a permission layer. Almost all failures - agentic runaway, hallucinations on tool results, prompt injection - live in one of those five boxes. Respect them.

The loop is the product

The model is interchangeable; the loop is what makes an agent an agent. Get the loop boring and solid before you optimize the model choice.

Step 1: Install the stack

uv init
uv add langchain langchain-openai langgraph

This installs the framework that now documents itself as “build resilient agents” - the low-level graph primitives behind most production agent stacks in 2026. Running against your local model from our Ollama guide:

from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    base_url="http://localhost:11434/v1",  # your local Ollama
    model="deepseek-v4-flash",
    temperature=0,
)

Step 2: Define tools and bind them

Keep the surface small - every tool is a new attack surface for prompt injection and a new failure mode to debug. Two well-typed tools beat ten loose ones.

from langchain.tools import tool

@tool
def multiply(a: int, b: int) -> int:
    """Multiply `a` and `b`."""
    return a * b

@tool
def add(a: int, b: int) -> int:
    """Adds `a` and `b`."""
    return a + b

tools = [add, multiply]
tools_by_name = {tool.name: tool for tool in tools}
model_with_tools = model.bind_tools(tools)

Step 3: State and the two nodes

State persists through the whole run. We use operator.add so messages append instead of replacing - the agent’s full history survives each loop iteration.

from langchain.messages import AnyMessage, SystemMessage, ToolMessage
from typing_extensions import TypedDict, Annotated
import operator

class MessagesState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]

def llm_call(state: MessagesState):
    """LLM decides whether to call a tool or not."""
    return {"messages": [model_with_tools.invoke(state["messages"])]}

def tool_node(state: MessagesState):
    """Performs the tool calls."""
    result = []
    for tool_call in state["messages"][-1].tool_calls:
        tool = tools_by_name[tool_call["name"]]
        observation = tool.invoke(tool_call["args"])
        result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
    return {"messages": result}

Step 4: Wire the loop and compile

If the last message carries tool calls, route back to the tool node; otherwise stop. That conditional edge is the entire ReAct loop, made explicit.

from typing import Literal
from langgraph.graph import StateGraph, START, END

def should_continue(state: MessagesState) -> Literal["tool_node", END]:
    if state["messages"][-1].tool_calls:
        return "tool_node"
    return END

agent_builder = StateGraph(MessagesState)
agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)
agent_builder.add_edge(START, "llm_call")
agent_builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
agent_builder.add_edge("tool_node", "llm_call")

agent = agent_builder.compile()

Add a step cap before you invoke

Wrap the invoke in a loop that counts iterations and throws past your budget - the graph runs until it converges, and an agent stuck looping on a tool result will happily spend your token budget. A hard cap is your kill switch.

Step 5: Memory and the human gate

Two additions turn this from a demo into something you can leave running:

  • Memory: attach a checkpointer from langgraph.checkpoint.memory so the agent recalls this session later - the official “memory & persistence” page shows the one-line change.
  • Human gate: wrap any write/delete tool in an interrupt() - the graph pauses mid-run, you approve in your terminal, and it resumes. Read-only tools skip the gate.

Prompt injection is real

Anything the agent reads - web pages, files, emails - is untrusted input. Treat model output as code to be validated: pass tool arguments through schema checks, never let fetched content rewrite your instructions. The step cap and the human gate are not optional.

Step 6: Ship it safe

Log every tool call with payloads and a trace ID. If your agent touches the network or the filesystem, run it inside Docker’s Sandboxes - disposable microVMs with filesystem and network controls built for exactly this - never on your bare host.

When you’re done

You have a stateful, safe, extensible agent that can answer research questions with citations from live search - the same pattern underpinning today’s enterprise agent platforms, and the same code path the official docs teach. This is Terminal-Bench 2.1’s test subject, and now it’s yours.

Questions, answered first

Do I need a big budget model for this to work?

No. A 30B-class model (or even a strong 8B) handles multi-step tool use fine when the loop is disciplined - see our local DeepSeek V4-Flash and Ollama guides. Reserve frontier models for the reasoning tasks, not the orchestration.

Why LangGraph instead of a raw loop?

Because the graph runtime gives you persistence, interrupts, streaming, and a step budget for free - the four things that separate a toy loop from something you can leave running. The official quickstart we mirror here is a supported path, not our invention.

What stops the agent from running away?

Three independent guards: a hard step cap, the interrupt gate on every non-read-only tool, and running inside an isolated sandbox when the agent touches the network. All three are non-negotiable before shipping.

Can I point this at my local Ollama instead of a hosted API?

Yes. Swap the model constructor for ChatOpenAI with base_url set to your Ollama endpoint (http://localhost:11434/v1) as shown in step 1. No other code changes - the graph, tools, and memory are model-agnostic.

Where do agents like this run safely in 2026?

For anything that reads web pages or runs commands, use Docker's Sandboxes - disposable microVMs built exactly for unsupervised coding agents, with filesystem and network controls. We covered it here.

You did it

  • `uv run` your agent and it completes a multi-step research task using at least two tools
  • It asks for confirmation before any write or delete action (interrupt, then resume)
  • Every tool call is logged with payloads and a trace ID
  • A step cap stops a runaway loop in a test
  • You can point the same code at a hosted model by changing only the base URL
Official sources