Use when deciding how much stock to hold and when to reorder it — classifying SKUs, sizing safety stock, setting reorder points and order quantities, and tracking the KPIs that keep a business from either stocking out or sitting on dead stock. NOT raising or negotiating the purchase order itself (that is `procurement`).
npx skills add https://github.com/ericrisco/rsc-harness --skill inventory
You are setting stock policy: how much to hold, when to reorder, how much to reorder. You are not buying it (that is procurement) and not moving it (that is logistics-ops). You decide the numbers that tell the buyer what to order and when.
Leave behind two checkable artifacts every time:
sku, abc_class, avg_demand, lead_time, safety_stock, reorder_point, order_qty, review_mode. This is the standing policy.on_hand <= reorder_point right now, with a suggested order quantity. This is the action.scripts/verify.sh checks both for shape and the structural invariants (ROP ≥ safety stock, order_qty > 0, no false triggers). Run it before you hand anything off. It validates structure, not whether the buffers are commercially smart — that judgment is yours.
The flow genuinely branches. Find the row that matches the ask before doing anything else.
| Ask sounds like | Job | You produce |
|---|---|---|
| "which SKUs deserve tight control", "run ABC" | Classify | ABC×XYZ class per SKU → the abc_class column + a policy posture per class |
| "how much safety stock", "we keep running out / sitting on dead stock" | Size the buffer | safety_stock per SKU, chosen Z, chosen formula |
| "what's my reorder point", "set up min/max", "weekly or continuous" | Set policy | reorder_point, order_qty, review_mode → the full policy table |
| "is our stock policy working", "turnover", "cut dead stock" | Track | KPI panel against the business's own history + cycle-count cadence |
Classify before you size, size before you set policy. A uniform policy across every SKU is the original sin (anti-patterns table).
Rule: never apply one uniform service level or review mode to every SKU — control is a budget, spend it where the value and the volatility are. Bad: 95% service on all 800 SKUs. Good: 99% on the high-value/stable AX items, make-to-order or near-zero buffer on the low-value/erratic CZ items.
annual_demand × unit_cost), sorted descending and cumulated. A ≈ top ~80% of value (usually ~20% of SKUs), B the next band, C the long low-value tail.CV = σ_demand / mean_demand. X = stable (low CV), Y = fluctuating, Z = erratic.The 9-cell matrix maps to policy: AX → tight automated min/max, highest service; CZ → manual, minimal buffer or make-to-order; AZ → high value but erratic, the candidate for DDMRP (below). Full cumulative-value worked sort, CV cut points, and the 9-cell→(review mode, service level, buffer posture) matrix are in references/abc-xyz.md.
Rule: match the formula to which thing actually varies. Picking the demand-only formula when the supplier's lead time swings is the most common under-buffering mistake — you buffer the wrong variance and still stock out.
demand varies, lead time stable: SS = Z · σ_demand · √LT
both vary, independent: SS = Z · √(LT · σ_demand² + avg_demand² · σ_LT²)
both vary, correlated (King): SS = Z · σ_demand · √LT + Z · avg_demand · σ_LT
Z is set by your target service level, never picked by feel:
| Service level | Z |
|---|---|
| 90% | 1.28 |
| 95% | 1.65 |
| 97.5% | 1.96 |
| 99% | 2.33 |
Higher service costs disproportionately more buffer — the tail is fat, so each extra point past ~98% buys a chunk of cash you reserve for A/critical items and deny to C items. Bad→Good: supplier "usually 5 days but sometimes 12" + you used Z·σ_demand·√LT → switch to the independent or King formula so σ_LT is in the buffer. How to estimate σ_demand and σ_LT from sales/receipt history, all three formulas worked with numbers, and the diminishing-returns curve are in references/safety-stock.md.
These are two separate decisions — conflating them is the classic beginner error. ROP answers *when*, order quantity answers *how much*.
ROP = (avg daily demand × lead-time days) + safety_stock ← when to trigger
EOQ = √(2 · D · S / H) ← how much to order
D = annual demand, S = cost per order, H = annual holding cost per unit
EOQ minimizes ordering + holding cost under stable demand. It is an order-*size* lever and never a trigger — you do not "wait until you can order an EOQ." Keep reorder_point and order_qty as separate columns.
Continuous vs periodic review is a real branch:
| Mode | How it works | Use when | Cost |
|---|---|---|---|
| Continuous (min-max / s,S) | Order the moment on-hand hits ROP s, bring up to S | A items, perpetual stock visibility | Fastest reaction, needs live counts |
| Periodic | Check on a fixed cadence (e.g. every Friday), order up to target | B/C items, batched POs, no live system | Needs a bigger buffer to cover the extra review-interval exposure |
Periodic review adds Z · σ_demand · √(review_interval) of exposure on top of lead-time exposure — budget for it. Worked ROP/EOQ examples, the review-interval buffer adjustment, and the exact policy-table + trigger-list schemas (the columns verify.sh checks) are in references/reorder-policies.md.
When a static ROP keeps whipsawing on erratic demand — you re-tune it monthly and it is still wrong — switch that SKU to buffer zones instead of a fixed point.
Trigger fires when net flow position = on_hand + on_order − qualified_demand drops from green into yellow. The zones recalculate as demand ramps, so you are not hand-editing safety stock every cycle.
Decision rule: reach for DDMRP only on erratic (Z-class), long-lead, or strategic SKUs — never for steady C items where a flat ROP is cheaper to run. Zone sizing and the net-flow trigger are in references/ddmrp.md. Set review_mode = ddmrp for these SKUs.
Prove the policy works with a KPI panel — and recompute every metric against the business's own history; the benchmarks below are sanity rails to flag against, not numbers to hallucinate or paste as targets.
| KPI | Formula | Rough rail |
|---|---|---|
| Inventory turnover | COGS / avg inventory | ≈6–8×/yr (consumer goods) |
| Days of inventory on hand | avg inventory / COGS × 365 | ≈30–60 days |
| GMROI | gross margin / avg inventory cost | target > 2.5 |
| Sell-through | units sold / units received | ≈70–85% / mo |
| Stockout rate | stockout events / order lines | target < 5% |
| Fill rate | lines filled complete / total lines | target > 95% |
| Dead-stock flag | SKUs with zero movement over a defined window | flag, don't reorder |
Cycle counting, not an annual full count. Count in a rolling sequence frequency-tiered off ABC: A items often (monthly/quarterly), C items rarely (annually). The warehouse never stops, and errors on high-value items surface fastest.
| Anti-pattern | Why it bites | Do instead |
|---|---|---|
| One uniform service level / review mode across all SKUs | Overspends on C, under-protects A | ABC×XYZ → differentiated Z and review mode |
| Demand-only SS when lead time is volatile | Buffers the wrong variance, still stocks out | Independent or King formula so σ_LT is in the buffer |
| Treating ROP and order quantity as one number | "Wait to order a full batch" → late triggers | Separate reorder_point (when) and order_qty (how much) columns |
| Using EOQ as a reorder trigger | EOQ is a size lever, not a signal | Trigger on ROP; size with EOQ |
| Picking Z by feel | Service level is undefined and indefensible | Set service level → read Z from the table |
| Static safety stock on erratic demand | Re-tuned monthly and still wrong | DDMRP buffer zones, review_mode = ddmrp |
| Annual full physical count | Warehouse stops; high-value errors found too late | ABC-tiered rolling cycle counts |
| Placing/negotiating the PO here | Wrong skill owns the buy | Hand the trigger list to procurement |
procurement (see ../procurement/SKILL.md). You hand it the trigger list; it executes the buy.Take ericrisco/inventory from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.