Agent Playbook, build tutorial

How to Build an AI Agent to Stop Stockouts

A walkthrough of how to build an inventory agent that watches stock and demand per SKU, forecasts what will sell, works out when and how much to reorder, picks the right supplier, and raises the purchase order, while also flagging the overstock and dead stock that freezes your cash. Every code step is shown in three frameworks, Google ADK, the Claude SDK, and LangGraph, so you can build it on the stack your team already knows. The code here is the agent's core logic; standing it up in production also means adding the setup, integrations, and hardening around it.

Why manual and rule-of-thumb reordering breaks

Reordering looks like a simple "when low, buy more," and that is exactly why it leaks money at both ends. Order too late and you stock out: the worldwide Corsten and Gruen study put the average out-of-stock rate at about 8.3 percent and the resulting loss at around 4 percent of sales. Order too much and cash freezes on the shelf: IHL Group puts total inventory distortion, out-of-stocks plus overstocks, at roughly $1.7 trillion globally, about 6.5 percent of retail sales.

A flat min-max rule cannot see a festive spike coming or a supplier running late. An agent is different: it forecasts demand per SKU, sizes a safety stock for the variability, triggers the order at the right level, chooses the supplier on landed cost and reliability, and flags the slow movers before they become dead stock.

Pick a framework: ADK, Claude SDK, or LangGraph

You do not build the agent loop from scratch. A framework gives you the moving parts: a step is a model plus an instruction plus a few typed tools, and the inventory maths are those tools. The code tabs on the relevant steps show the same read, forecast, check, and order flow three ways.

Google ADK gives the deterministic functions to an agent and runs it with a Runner, the Claude SDK drives a tool-use loop, and LangGraph builds an explicit per-SKU graph. The forecast, the reorder-point maths, and the EOQ are the same code in all three; only the orchestration and the supplier- quote reading differ.

The architecture

Every SKU flows through one loop: read stock and sales, forecast demand, compute the reorder point, then either raise a purchase order when below it or watch and flag overstock when above. Big spend routes to a human, and guardrails for duplicate orders, MOQ, and audit wrap the whole thing.

1 to 2. Read stock and sales on-hand, velocity, open POs 3. Forecast demand per SKU, with seasonality 4. Reorder point + qty safety stock, EOQ 5. Below reorder point? covered through lead time? 6. Raise the PO pick supplier, send Watch and flag overstock, dead stock 7. Monitor fill rate, stockout days Guardrails no double-PO MOQ, shelf life audit log below, reorder covered dips below later feedback loop

Before you build: prerequisites

Data you need

Per-SKU on-hand, daily sales history, open purchase orders, supplier lead times, MOQs, and any shelf life or expiry.

Systems to connect

Your POS and ERP (Tally, Zoho Inventory, Unicommerce, Vyapar), a model API key for reading quotes, your purchasing module, and your vendor list.

Policy

Target service level, auto-approve spend limit, overstock and dead-stock thresholds, and who signs off on large orders.

Setup: install and keys

Install the packages and set your Gemini key. That is enough to run the runnable file below, which forecasts and sizes a reorder for a sample SKU locally. For production you connect your POS or ERP API (Tally, Zoho Inventory, Unicommerce) for live stock and sales, and your purchasing system to raise the order; those are provider-specific.

Shell (install and key)
# Python 3.10 or newer
python -m venv venv && source venv/bin/activate

# The runnable path below uses Google ADK + Gemini:
pip install google-adk google-genai

# Your Gemini key from aistudio.google.com/apikey:
export GEMINI_API_KEY="your-key-here"

The stack, and why each piece

Layer Pick Why
Signals Your POS and ERP API (Tally, Zoho Inventory, Unicommerce, Vyapar) Pull current on-hand, open purchase orders, and daily sell-through per SKU. The reorder decision is only as good as these numbers.
Forecast A baseline in statsmodels or Prophet, lifted by a seasonality index Predict demand per SKU instead of guessing from a flat average, so the festive spike does not catch you empty.
Decision core Plain Python for reorder point, safety stock, and EOQ The maths is deterministic and testable. You never want a model freelancing on how much stock to buy.
Model Gemini 2.5 Flash, Claude, or any model behind LangChain Reads supplier quotes and price lists straight from the PDF or email into a schema. The code tabs show the same call in all three.
Agent framework Google ADK, the Claude SDK, or LangGraph Drives each SKU through read, forecast, check, and order, calling the deterministic tools. Pick the one your team runs.
Data Postgres plus a dashboard Store forecasts, reorder points, decisions, and an audit trail, and track fill rate and stockout days over time.

Build it, step by step

The model and orchestration steps show the same logic in Google ADK, the Claude SDK, and LangGraph. Use the tabs to switch. The inventory maths are deterministic Python and identical everywhere. These are the building blocks, and they call your own systems (the POS, the ERP, the purchasing module) by name; the complete file you can run today is in "Put it together" below.

1

Read stock and sales into one SKU view

Every decision starts from clean numbers, so the agent pulls them per SKU from your POS and ERP: current on-hand, what is already on order in open purchase orders, and the recent daily sell-through. Counting on-order stock matters, because an item can be below its shelf level yet already covered by a PO arriving tomorrow. This is the same in every framework.

Python (POS / ERP API)
from pydantic import BaseModel

class SkuState(BaseModel):
    sku: str
    on_hand: int
    on_order: int                          # already in an open PO
    daily_sales: list[int]                 # recent units/day, newest last
    lead_time_days: int
    shelf_life_days: int | None = None

def load_state(sku: str) -> SkuState:
    pos = erp.stock(sku)                    # current on-hand and open POs
    hist = pos_sales.daily(sku, days=90)   # last 90 days of sell-through
    sup = vendors.primary(sku)
    return SkuState(sku=sku, on_hand=pos.on_hand, on_order=pos.on_order,
                    daily_sales=hist, lead_time_days=sup.lead_time_days)
2

Forecast demand per SKU

A flat average is what gets shops caught empty before Diwali. The agent forecasts demand per SKU, starting from a recent trend and lifting it by a seasonality index for the month or festival. As history builds, you swap the baseline for a proper time-series model such as Prophet without changing anything downstream. Deterministic, the same in every framework.

Python (forecast)
import statistics

def forecast_daily(s: SkuState) -> float:
    recent = s.daily_sales[-28:]                  # last 4 weeks
    base = statistics.mean(recent) if recent else 0
    season = seasonality_index(s.sku)             # e.g. 1.3 in festive months
    return base * season
3

Compute the reorder point and safety stock

The reorder point is the level at which you must order so new stock lands before you run out: expected demand across the lead time, plus a safety stock that absorbs the days demand runs hot or the supplier runs late. Safety stock is sized from the variability of recent sales and your target service level, where a z of about 1.65 covers roughly 95 percent of cases. This is deterministic code.

Python (inventory maths)
import math

def reorder_point(s: SkuState, daily: float, z: float = 1.65) -> int:
    sigma = statistics.pstdev(s.daily_sales[-28:] or [0])
    safety = z * sigma * math.sqrt(s.lead_time_days)
    return math.ceil(daily * s.lead_time_days + safety)
4

Decide how much to order

Ordering too little means ordering again next week and paying freight twice; ordering too much freezes cash. The economic order quantity balances ordering cost against holding cost, then the agent caps it by the supplier's minimum order quantity and, for perishables, by what you can realistically sell before it expires.

Python (EOQ)
def order_qty(s: SkuState, annual_demand: float, order_cost, hold_cost) -> int:
    eoq = math.sqrt((2 * annual_demand * order_cost) / hold_cost)
    qty = max(eoq, vendors.primary(s.sku).moq)        # respect MOQ
    if s.shelf_life_days:
        qty = min(qty, perishable_cap(s, annual_demand))  # do not over-buy
    return math.ceil(qty)
5

Pick the right supplier by reading the latest quote

The cheapest unit price is not always the right order, and quotes arrive as PDFs and emails, not tidy rows. The model reads each supplier's latest quote directly into a typed schema, then the agent ranks vendors on landed cost, lead time, and reliability. The tabs show the same quote read in Gemini, Claude, and LangChain; the schema is defined once and reused.

from google import genai
from google.genai import types
from pydantic import BaseModel

client = genai.Client()

class Quote(BaseModel):
    vendor: str
    unit_price: float
    lead_time_days: int
    moq: int

def read_quote(pdf: bytes) -> Quote:
    resp = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=[types.Part.from_bytes(data=pdf, mime_type="application/pdf"),
                  "Extract the quote into the schema."],
        config=types.GenerateContentConfig(
            response_mime_type="application/json", response_schema=Quote))
    return resp.parsed

def choose_vendor(sku: str, qty: int) -> Quote:
    quotes = [read_quote(q) for q in vendors.latest_quotes(sku)]
    return min(quotes, key=lambda v: (v.unit_price * qty, v.lead_time_days,
                                      -vendors.on_time_rate(v.vendor)))
import base64
from anthropic import Anthropic

client = Anthropic()
# Quote is the same Pydantic schema as the Gemini tab.

def read_quote(pdf: bytes) -> Quote:
    msg = client.messages.parse(
        model="claude-opus-4-8", max_tokens=512,
        messages=[{"role": "user", "content": [
            {"type": "document", "source": {"type": "base64",
             "media_type": "application/pdf",
             "data": base64.standard_b64encode(pdf).decode()}},
            {"type": "text", "text": "Extract the quote into the schema."}]}],
        output_config={"format": Quote})
    return msg.parsed_output
import base64
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.messages import HumanMessage

# Quote is the same Pydantic schema as the Gemini tab.
reader = ChatGoogleGenerativeAI(model="gemini-2.5-flash").with_structured_output(Quote)

def read_quote(pdf: bytes) -> Quote:
    return reader.invoke([HumanMessage(content=[
        {"type": "text", "text": "Extract the quote into the schema."},
        {"type": "media", "mime_type": "application/pdf",
         "data": base64.b64encode(pdf).decode()}])])
6

Raise the purchase order

Now the loop comes together. If on-hand plus on-order is still above the reorder point, the agent does nothing, which is most of the time. When it dips below, the agent sizes the order, picks the supplier, and drafts the PO. Routine, low-value orders send automatically, while anything above your spend limit waits for a human.

Python (reorder)
def maybe_reorder(s: SkuState):
    daily = forecast_daily(s)
    rop = reorder_point(s, daily)
    if s.on_hand + s.on_order > rop:
        return                                  # still covered, do nothing
    qty = order_qty(s, annual_demand=daily * 365, order_cost=250, hold_cost=18)
    vendor = choose_vendor(s.sku, qty)
    po = purchasing.draft_po(s.sku, qty, vendor)
    if po.value > policy.auto_approve_limit:
        review_queue.add(po, reason="over_limit")   # human approves big spend
    else:
        purchasing.send(po)                     # auto-send small, routine POs
7

Flag overstock and dead stock

Stockouts are only half of inventory distortion; the other half is cash frozen in things that do not sell. On the same pass, the agent measures days of cover and flags anything with far too much as overstock to mark down or return, and items selling nothing for a long stretch as dead stock to liquidate.

Python (deterministic)
def flag_dead_stock(s: SkuState, daily: float):
    days_cover = s.on_hand / daily if daily else math.inf
    if days_cover > policy.overstock_days:
        flag(s.sku, "overstock", action="markdown_or_return")
    if daily == 0 and s.on_hand > 0 and age(s.sku) > policy.dead_days:
        flag(s.sku, "dead_stock", action="liquidate")
8

Guardrails: no double-orders, monitoring, alerts

Anything that spends money has to be safe to run on a schedule. Before drafting, the agent checks for an existing open PO so a retry never stacks duplicate orders. It avoids leaning on a single slow supplier when a faster alternative exists, respects MOQ and shelf life as hard limits, and writes every decision to an append-only audit log. A dashboard tracks fill rate, stockout days, inventory value, and dead-stock value.

Python (guardrails)
def guard(s: SkuState, vendor, qty) -> "Vendor":
    if purchasing.open_po_exists(s.sku):
        raise SkipReorder("open_po")            # never stack duplicate POs
    if vendor.lead_time_days > policy.max_lead and alternatives(s.sku):
        vendor = choose_vendor(s.sku, qty)      # avoid a single slow supplier
    return vendor

# audit.append(s.sku, decision); metrics.track(fill_rate, stockout_days,
#   inventory_value, dead_stock_value); alert_if(fill_rate < target)
9

Assemble and run the reorder agent

Now wire the tools into one runnable agent that sweeps the catalogue. ADK gives the deterministic functions to an LlmAgent and runs it with a Runner, the Claude SDK drives a tool-use loop, and LangGraph builds an explicit per-SKU graph that branches to order only when stock is below the reorder point. The tools and the inventory maths are the same in all three.

from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService

reorder_agent = LlmAgent(
    name="reorder_agent", model="gemini-2.5-flash",
    instruction="For each SKU below its reorder point, size the order with order_qty, "
                "pick the supplier with choose_vendor, and call maybe_reorder. "
                "Flag overstock and dead stock.",
    tools=[forecast_daily, reorder_point, order_qty, choose_vendor,
           maybe_reorder, flag_dead_stock])

runner = Runner(agent=reorder_agent, app_name="inventory",
                session_service=InMemorySessionService())
# Run daily over the catalogue via cron / Cloud Scheduler.
from anthropic import Anthropic
client = Anthropic()
TOOLS = [forecast_tool, rop_tool, eoq_tool, vendor_tool, reorder_tool]  # JSON schemas

def run_reorder(messages: list):
    while True:
        resp = client.messages.create(model="claude-opus-4-8", max_tokens=1024,
                                       tools=TOOLS, messages=messages)
        if resp.stop_reason != "tool_use":
            return resp
        messages.append({"role": "assistant", "content": resp.content})
        for b in resp.content:
            if b.type == "tool_use":
                out = dispatch(b.name, b.input)
                messages.append({"role": "user", "content": [
                    {"type": "tool_result", "tool_use_id": b.id, "content": str(out)}]})
from langgraph.graph import StateGraph, START, END

def assess(s: dict) -> dict:
    st = load_state(s["sku"]); daily = forecast_daily(st)
    rop = reorder_point(st, daily)
    return {"state": st, "daily": daily, "below": st.on_hand + st.on_order <= rop}

def order(s: dict) -> dict:
    return {"po": maybe_reorder(s["state"])}

g = StateGraph(dict)
g.add_node("assess", assess); g.add_node("order", order)
g.add_edge(START, "assess")
g.add_conditional_edges("assess", lambda s: "order" if s["below"] else END)
g.add_edge("order", END)
reorder_agent = g.compile()      # run per SKU on a daily sweep

Put it together: run it end to end

Here is a runnable file. It forecasts demand for a sample SKU, computes its reorder point, and sizes an order if stock is below it, then prints the decision. Replace SKUS with your POS or ERP feed. Save it as main.py and run python main.py.

Python (full runnable file, ADK)
# main.py  --  run:  python main.py
# A runnable reference: forecasts demand for one SKU, computes its reorder point,
# and sizes an order if it is below it. Replace SKUS with your POS/ERP feed.
import asyncio, math, statistics
from google import genai
from google.genai import types
from google.adk.agents import LlmAgent
from google.adk.runners import InMemoryRunner

client = genai.Client()                       # reads GEMINI_API_KEY

# ---- a sample catalogue (replace with your POS/ERP feed) ----
SKUS = {
    "SKU-1180": {"on_hand": 42, "on_order": 0, "moq": 24, "lead_time_days": 6,
                 "daily_sales": [9, 11, 10, 12, 11, 13, 10, 12, 11, 10, 12, 11]},
}
# ------------------------------------------------------------

def reorder_check(sku_id: str) -> dict:
    """Forecast one SKU, compute its reorder point, and size an order if below it."""
    s = SKUS[sku_id]
    recent = s["daily_sales"][-28:]
    daily = statistics.mean(recent) if recent else 0
    sigma = statistics.pstdev(recent or [0])
    rop = math.ceil(daily * s["lead_time_days"]
                    + 1.65 * sigma * math.sqrt(s["lead_time_days"]))   # 95% service level
    if s["on_hand"] + s["on_order"] > rop:
        return {"sku": sku_id, "action": "hold", "reorder_point": rop,
                "on_hand": s["on_hand"]}
    eoq = math.ceil(math.sqrt((2 * daily * 365 * 250) / 18))   # order cost 250, hold 18
    return {"sku": sku_id, "action": "reorder", "reorder_point": rop,
            "qty": max(eoq, s["moq"])}

agent = LlmAgent(
    name="reorder_agent", model="gemini-2.5-flash",
    instruction=("Call reorder_check on the SKU the user names, then report whether "
                 "to reorder, the reorder point, and the quantity if any."),
    tools=[reorder_check])

async def main():
    runner = InMemoryRunner(agent=agent, app_name="inventory")
    session = await runner.session_service.create_session(app_name="inventory", user_id="demo")
    msg = types.Content(role="user", parts=[types.Part(text="Check SKU-1180.")])
    async for event in runner.run_async(user_id="demo", session_id=session.id, new_message=msg):
        if event.is_final_response():
            print(event.content.parts[0].text)

if __name__ == "__main__":
    asyncio.run(main())

You should see the agent report the reorder point and, for this sample, a reorder with a quantity, since on-hand is below the point. Supplier selection from real quotes and the purchase order itself stay in the build steps above, which need your vendor and purchasing systems.

The reorder view

The buyer sees one screen: what is low, the numbers behind it, and the recommended order with a supplier already chosen.

Inventory NEEDS ACTION (3) SKU-1180LOW, reorder SKU-2245OK, covered SKU-0093DEAD, liquidate SKU-1180 On hand42 units Daily sales11 / day Lead time6 days Reorder point96 units Below reorder point RECOMMENDATION Order qty120 units SupplierVendor B Landed costRs 48,000 Arrivesin 5 days Best on cost and lead time Approve PO Snooze

Build, buy, or spreadsheet

Approach Best for Effort Cost Control
Custom agent (build in-house) You want your own forecast, multi-supplier rules, and tight ERP fit High Build cost + compute Full
Inventory platform (Zoho Inventory, Unicommerce, Increff, NetSuite) Standard reordering and stock control, fast start Low Subscription per seat or volume Medium
Spreadsheet min-max A small catalogue with steady, predictable demand Low Cheap, but blind to variability and lead-time risk Low

What naive reordering gets wrong

The mistakes that cause stockouts are almost always about variability, not averages. Demand is not flat: it spikes around festivals, paydays, and promotions, so a forecast without seasonality plans for a normal week and gets caught in a busy one. Lead times are not fixed either, so safety stock has to absorb both demand running hot and a supplier running late, which is why it scales with the square root of the lead time, not the average alone.

The other classic errors are structural. Ignoring stock already on order leads to double-buying; ignoring MOQ and shelf life leads to overstock and waste; leaning on one cheap but slow supplier turns a small saving into a lost sale. The agent encodes each of these as an explicit rule, so the decision is defensible rather than a guess that happened to work last time.

The numbers

Benchmarks vary by source and should be treated as ranges, not promises. These are from neutral research, not vendor marketing.

Metric Manual Agent
Average out-of-stock rate ~8.3% of items globally (Corsten and Gruen worldwide study) Lower, because reorder points fire before the shelf empties
Sales lost to stockouts ~4% of sales on average (Corsten and Gruen) Recovered as on-shelf availability rises
Cash frozen in overstock Overstocks cost retailers about $554 billion globally (IHL Group) Flagged early for markdown or return, not left to rot
Total inventory distortion ~$1.7 trillion globally, about 6.5% of retail sales (IHL Group) Shrinks as forecasts and reorder points tighten on both ends

Sources: Corsten and Gruen, Retail Out-of-Stocks worldwide study; IHL Group inventory distortion research. Figures are industry ranges.

What makes it fail

  • !Reordering on a flat average, ignoring demand and lead-time variability.
  • !No safety stock, so any spike or supplier delay becomes a stockout.
  • !Not counting stock on order, so it double-buys what is already coming.
  • !Ignoring MOQ and shelf life, so it overstocks or wastes perishables.
  • !Leaning on one cheap but slow supplier with no faster fallback.

A safe rollout

  1. Start as alerts only: compute reorder points and surface low stock and dead stock, a human orders.
  2. Auto-draft purchase orders for routine, low-value SKUs, with a human approving big spend.
  3. Turn on auto-send for trusted SKUs and suppliers under your limit.
  4. Add seasonal forecasting and multi-supplier selection, then scale to all SKUs with a service-level dashboard.

FAQs

General FAQs

Everything you need to know about the service and how it works. Can’t find an answer? Mail us at info@galific.com

  • How is the reorder point actually calculated?
    The reorder point is the expected demand across the supplier's lead time plus a safety stock. Safety stock is sized from the variability of your recent sales and your target service level, where a z value of about 1.65 covers roughly 95 percent of cases. It is deterministic code, so you can inspect and test exactly why each SKU's reorder point is what it is, rather than trusting a black box.
  • Which framework should I use: ADK, the Claude SDK, or LangGraph?
    The right framework depends on your team, and the agent logic is the same in all three. The code tabs let you read the identical read, forecast, check, and order flow in Google ADK, the Anthropic SDK, and LangGraph and pick the one you already know. ADK and LangGraph are model-agnostic, so you can run Gemini or Claude under either; the inventory maths is the same code regardless.
  • Does it forecast demand, and can it handle the festive season?
    Yes. It forecasts demand per SKU from a recent trend lifted by a seasonality index, so the Diwali or wedding-season spike is anticipated rather than discovered when the shelf is empty. The baseline is explainable, and as you accumulate history it can be upgraded to a proper time-series model such as Prophet without changing the rest of the loop.
  • How does it choose which supplier to order from?
    It reads each vendor's latest quote, which usually arrives as a PDF or email, directly into a typed schema, then compares them on landed cost for that quantity, lead time, and on-time reliability tracked from past orders. So it will not pick a marginally cheaper supplier who delivers two weeks late and causes a stockout. The ranking improves over time as the agent learns each vendor's real reliability.
  • Does it raise the purchase order automatically?
    Yes, with a control you set. Routine, low-value orders are drafted and sent automatically, while anything above your spend limit is held for a human to approve. That gives you hands-off restocking on the long tail of small SKUs and human control where the money is large, which is the balance most businesses actually want.
  • How does it avoid ordering the same thing twice?
    It counts stock already on order and checks for an existing open purchase order before drafting a new one, so an overlapping run or a retried job can never stack duplicate orders. Counting on-order stock also stops it reordering an item that is below its shelf level but already covered by a PO arriving shortly.
  • Can it handle perishables, minimum order quantities, and shelf life?
    Yes, these are hard limits in the decision. The order quantity respects the supplier's minimum order quantity and, for perishables, is capped at what can realistically sell before expiry, so you neither breach the supplier's terms nor buy stock that will be written off. Shelf life also feeds the dead-stock and overstock checks.
  • What does it do about overstock and dead stock?
    On every pass it measures days of cover and flags items with far too much as overstock to mark down or return, and items selling nothing for a long stretch as dead stock to liquidate. This is the other half of inventory distortion, the cash frozen in things that do not move, and catching it early is often where the fastest savings come from.
  • Does it integrate with Tally, Zoho Inventory, or Unicommerce?
    Yes, through each system's API. It reads on-hand, open POs, and sales from your ERP or POS and writes purchase orders back, for Tally, Zoho Inventory, Unicommerce, Vyapar, NetSuite, and similar systems. Where a system has no clean API, it can exchange validated files so your inventory records stay the single source of truth.
  • How much data do I need before it is useful?
    It works from day one with simple averages and your known lead times, and gets sharper as sales history accumulates. A few weeks of daily sell-through is enough for a sensible reorder point, and a season or two of history lets the forecast capture your real demand pattern. You do not need a data science team to start.
  • Should I build a custom agent or buy an inventory platform?
    Buy a platform if your reordering is standard and you want stock control quickly with little engineering. Build, or have it built, when you need your own forecast, multi-supplier selection, perishable and budget rules, and tight ERP integration. See the comparison table above; the right answer depends on your catalogue size and how custom your buying logic is.
  • What does it cost to run, and how fast is payback?
    Running cost is mostly compute and integration, which is small next to the sales recovered from fewer stockouts and the cash freed from less overstock. Against benchmarks where retailers lose around 4 percent of sales to out-of-stocks and the industry carries roughly $1.7 trillion in inventory distortion, even a modest improvement in fill rate usually pays back the build within a few months.

Want this built and running in your stack?

Galific designs, builds, and runs inventory agents like this on ADK, the Claude SDK, or LangGraph, wired into your POS, your ERP, and your suppliers. Or explore the ready-made versions, from Low Stock Alert to the Demand Planner and JIT Restock, in our agent suite.