Learning Deep Agents by building a Claim Processing Agent — Applied AI #3 LangChain's open-source Deep Agents library, built on LangChain and running on the LangGraph runtime, provides an agent harness that adds subagents, a file system and planning to the standard tool-calling loop, and a tutorial demonstrates it by building an expense claim processing agent for a fictional company, Acme Inc. The tutorial's agent uses two custom tools — read_pdf, which extracts receipt text via pdfplumber, and calculate, a restricted arithmetic evaluator — alongside Deep Agents' default file tools (ls, read_file, write_file, glob, grep), and runs on Google's gemini-3.5-flash model. The sample code and data, including claim requests, employee records, reimbursed claim history and Acme's expense policy, are published in the GitHub repository akshay-r13/claims-processing-deep-agent. Some agent tasks are too big for one loop. The agent has to plan, read a lot of material, keep intermediate results somewhere, and not lose track along the way. If you’ve used Claude Code or Claude Cowork, you’ve seen this work: you hand over a broad objective, the agent plans it, executes, and adjusts the plan as it learns more. Deep Agents https://www.langchain.com/deep-agents is LangChain’s open-source library that brings this pattern to your own agents, on any model. It’s built on LangChain and runs on the LangGraph runtime, so everything you know about LangGraph still applies. LangChain calls it an agent harness : the usual tool-calling loop, plus built-in capabilities around it: These sound abstract until you hit the problem they solve. So let’s hit it. Every company has an expense reimbursement process. Employees pay out of their pocket on work-related things and submit an expense claims to get it back. Typically this includes: Business Travel, Lodging, Meals, Office Supplies, Courses, Certifications etc. Each claim comes with supporting documents such as receipts, invoices and tickets. HR or Finance reviews every claim against these documents and decide an action. We’ll build an agent that does that review. It has to: Each requirement maps to one of the capabilities above. Requirement 1 is a job for subagents, requirement 2 for the file system, and requirement 3 for planning. With plain LangChain and LangGraph, you’d hand-build all of that plumbing before writing a line of claims logic. Here it comes with the library. By the end, you’ll have a working agent that reads receipts, investigates each claim, and writes a report for a human reviewer. Let’s build it. For this post I created sample receipts, claim requests, claim history, and a company policy for a fictional company, Acme Inc. Link to the Github repo — https://github.com/akshay-r13/claims-processing-deep-agent https://github.com/akshay-r13/claims-processing-deep-agent Following is the repo layout: claims-processing-deep-agent/├── config/paths.py DATA DIR, CLAIM REQUESTS LIST├── data/│ ├── claim policy docs/acme expense policy.md│ ├── claim requests/claim requests.csv new claim requests│ ├── employees/employees.csv│ ├── claim history/reimbursed claims.csv│ └── claim requests/submitted documents/CLM-1001/ .pdf├── agent/│ ├── init .py│ ├── tools.py│ ├── subagents.py│ ├── agent.py├── process claim.py└── .env Install the dependencies and set your Gemini key: pip install deepagents langchain-google-genai pdfplumber python-dotenv .env GOOGLE API KEY=your-keyCLAIMS MODEL=google genai:gemini-3.5-flash The agent has four parts: tools, a subagent, the main agent, and a runner. Deep Agents come with default file tools ls, read file, write file, glob, grep that work on text. Our receipts are PDFs, and the agent can’t run code, so we add two tools. read pdf extracts a receipt’s text. calculate is a restricted arithmetic evaluator. Models make arithmetic mistakes on tax, tip, and caps, so every sum goes through this tool. python import astimport operatorfrom pathlib import Pathfrom config.paths import DATA DIRimport pdfplumberfrom langchain core.tools import tool@tooldef read pdf path: str - str: """Extract the text of one PDF under /data e.g. /data/claims/CLM-1004/receipts/meal 2026-10-14 dinner.pdf. Returns plain text.""" try: full = resolve data path path except ValueError as e: return f"ERROR: {e}" if not full.is file or full.suffix.lower = ".pdf": return f"ERROR: not a PDF file: {path}" with pdfplumber.open full as pdf: text = "\n".join page.extract text or "" for page in pdf.pages return text.strip or "ERROR: no extractable text scanned image? needs OCR or a vision model "@tooldef calculate expression: str - str: """Evaluate a python arithmetic expression. Supports + - / parentheses and min , max , round . Use it for EVERY sum, percentage, cap and comparison; never do arithmetic yourself. Example: min 302.40, 6 50 """ try: result = eval ast.parse expression, mode="eval" .body except Exception as e: return f"ERROR: {e}" return f"{round result, 2 :.2f}"def resolve data path virtual path: str - Path: """Map agent path under /data/ to real file. Reject anything that escapes the data directory.""" rel = virtual path.removeprefix "/data" .lstrip "/" full = DATA DIR / rel .resolve root = DATA DIR.resolve if full = root and root not in full.parents: raise ValueError f"Path outside data directory: {virtual path}" return fulldef eval node : SUPPORTED OPERATIONS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv} SUPPORTED FUNCTIONS = {"min": min, "max": max, "round": round} Plain numeric value, base condition if isinstance node, ast.Constant and isinstance node.value, int, float : return node.value + , - , x and / operations if isinstance node, ast.BinOp and type node.op in SUPPORTED OPERATIONS: return SUPPORTED OPERATIONS type node.op eval node.left , eval node.right negative inversion if isinstance node, ast.UnaryOp and isinstance node.op, ast.USub : return -eval node.operand functions min max and round if isinstance node, ast.Call and isinstance node.func, ast.Name and node.func.id in SUPPORTED FUNCTIONS and not node.keywords : return SUPPORTED FUNCTIONS node.func.id eval a for a in node.args raise ValueError "unsupported expression" calculate is not a raw eval. It only accepts numbers, four operators, and min, max, and round. resolve data path keeps the agent inside the data folder. This step solves the context-window problem. A 3-day trip has 14 receipts, and putting all their raw text into one agent’s context would bury it. Instead, each receipt goes to a subagent that reads it in an isolated context, saves the extracted fields as JSON to the file system, and returns a single line. python from .tools import calculate, read pdfRECEIPT READER = { "name": "receipt-reader", "description": "Reads ONE receipt PDF and saves its extracted fields as JSON. " "Give it the receipt path and the output path. " "It returns a one-line summary, not the receipt text." , "system prompt": """You extract data from one receipt PDF. 1. Call read pdf on the path you are given. 2. Build a JSON object with: file, vendor, receipt no, date, line items list of {description, amount} , subtotal, tax, tip, total, payment method, notes. Put anything unusual in notes mismatched totals, 'copy' markers, missing fields . Use null for absent fields. Do not invent values. 3. Use calculate to check that line items, tax and tip add up to the total. Record any mismatch in notes. 4. Save the JSON with write file to the output path you were given. 5. Reply with ONE line: vendor | date | total | output path. Never return the raw receipt text.""", "tools": read pdf, calculate ,} A subagent is simply a dictionary with a name, a description, a system prompt, and its own tools. The main agent reads the description to decide when to delegate. The “reply with ONE line” rule is the important part, because a subagent that returned everything it read would defeat the purpose. The main agent needs a file backend, a system prompt, and the wiring. Here is the design choice that matters most. A CompositeBackend routes by path: anything under /data/ and /reports/goes to a real folder on disk, and everything else /work goes to an in-memory state backend. The agent’s scratch files never touch our repo. python import osfrom deepagents import create deep agentfrom deepagents.backends import CompositeBackend, FilesystemBackend, StateBackendfrom .subagents import RECEIPT READERfrom .tools import calculate, read pdffrom config.paths import DATA DIR, OUTPUT DIRfrom langchain.chat models import init chat modelMODEL = init chat model os.environ "CLAIMS MODEL" , include thoughts=True SYSTEM PROMPT = """You are an expense claims reviewer at Acme Inc. You review one claim at a timeagainst the company policy and its receipts, then decide what happens next.Files:- /data is READ-ONLY reference data: the policy markdown , employees.csv, the reimbursement history CSV, and the receipt PDFs for each claim. Use ls and glob to find what you need.- /work is your scratch space. /reports is where the final report goes. Write only there.Workflow:1. Make a plan with write todos before doing anything else.2. Read the policy in full. Look up the employee and any prior reimbursements in the CSVs.3. List the claim's receipt PDFs. Delegate each receipt to the receipt-reader subagent, one receipt per task call, and run them in parallel. Each saves its fields to /work/