Inventory & Availability
com.os.retailagent.shopping.inventory
Plane 1 · Discovery & Truth · Tier 1 · Qualified · Reference implementation in Playground
Abstract
An AI agent that confidently sells an out-of-stock item is worse than an agent that doesn't sell at all. This spec defines how a merchant declares inventory state at catalog time — in-stock, low-stock, out-of-stock, backorder, or preorder — and how an agent computes availability before recommending or adding to cart.
The spec adds two properties that UCP does not carry: freshness (a mandatory TTL envelope — inventory is the most time-sensitive data in the pipeline, default 60s) and soft reservation semantics (an add-to-cart action can place a timed hold on stock, and an expired hold triggers a re-evaluation signal before checkout).
The problem this solves
- An agent recommends an item; the buyer adds it; checkout rejects with "out of stock." The agent had no way to know.
- Two agents race for the last unit — the first checkout wins, the second fails. The agent can't explain why.
- A backorder item shows as "unavailable" with no ETA — the agent can't tell the buyer when to expect it.
- A buyer wants store pickup; the item is available at store A but not store B. The agent has no location-level signal.
- Stale inventory data (cached at recommending time) is served at checkout — prices are fresh but stock figures are 10 minutes old.
What goes in
Merchant declares on the variant
Stock state, quantity, per-location breakdown, backorder ETA, preorder release date, TTL, and reservation policy.
Agent supplies buyer context
Fulfillment mode (shipping vs pickup) determines whether per-location filtering applies.
Pipeline injects time
The injected now timestamp drives freshness checks and reservation expiry — never Date.now().
What comes out
Availability state
in-stock / low-stock / out-of-stock / backorder / preorder — with optional onlyXLeft count and per-location breakdown.
Freshness envelope
computedAt + TTL in seconds. Mandatory for inventory. After TTL expires, STOCK_STALE signals the agent to re-fetch before acting.
Reason code registry
One namespace, seven codes. Every availability decision is machine-readable.
| Code | Meaning | Severity | Resolvable? |
|---|---|---|---|
OUT_OF_STOCK | Item is out of stock. Cannot be added to cart. Resolution: notify-me (RAOS-0013 intent capture forward-ref). | BLOCK | Request notify-me alert (RAOS-0013) |
LOW_STOCK | Item is in stock but quantity is at or below the low-stock threshold. Agent should surface urgency ("Only 3 left"). | INFO | Advisory |
BACKORDER_AVAILABLE | Item is on backorder but orderable. ETA is carried in requirements[]. Agent should surface the ETA to the buyer. | CONDITION | Acknowledge backorder ETA before ordering |
PREORDER_NOT_YET_BUYABLE | Item is visible for preorder but cannot be added to cart until release date. Release date in requirements[]. | CONDITION | Wait for release date |
STOCK_STALE | Inventory data has expired its TTL. The agent must re-fetch before acting. Does not block the item — signals a re-fetch requirement. | CONDITION | Re-fetch inventory data |
LOCATION_OUT_OF_STOCK | At least one pickup location has stock, but the buyer's selected/preferred location does not. Available locations listed in requirements[]. | CONDITION | Select a pickup location with available stock |
RESERVATION_EXPIRED | A soft hold placed at add-to-cart has expired. The buyer must re-evaluate availability before checkout. | BLOCK | Re-evaluate availability; re-add to cart if still in stock |
How availability is computed
Check freshness first. Then check whether any reservation holds have expired. Then evaluate the stock state: block on OOS, surface ETA on backorder, surface release date on preorder, signal urgency on low-stock. For pickup buyers, filter by per-location stock. Return the result with a freshness timestamp — the agent knows exactly when to re-fetch.
Real merchant scenarios
Fresh Corner Market — BOPIS per-location stock (Grocery)
Shipping buyer
in_stock — no location filtering; ships from warehouse
Pickup buyer (any location)
LOCATION_OUT_OF_STOCK (CONDITION) — store A has it, store B doesn't. Buyer selects store A.
Pickup buyer (all locations zero)
OUT_OF_STOCK (BLOCK) — no locations have stock
Atlas Wholesale — backorder with ETA (B2B)
Wholesale buyer queries availability
BACKORDER_AVAILABLE (CONDITION) — ETA 2026-08-15. Buyer can order now.
Hard OOS (no ETA)
OUT_OF_STOCK (BLOCK) — resolution: notify-me. No orderability.
Sara's Boutique — preorder (DTC)
Any buyer before release
PREORDER_NOT_YET_BUYABLE (CONDITION) — visible but not yet addable. Release date: 2027-03-01.
Open questions
These are genuine forks. Tell me I'm wrong.
#1Per-location vs buyer-location mapping: should RAOS-0005 know which locationId maps to the buyer's region?
Currently the spec surfaces the full perLocation[] array and emits LOCATION_OUT_OF_STOCK when the buyer is in pickup mode and some locations have zero stock. But the spec does not know which locationId corresponds to the buyer's physical location — that mapping is RAOS-0003's (Fulfillment Feasibility) concern. Should RAOS-0005 emit LOCATION_OUT_OF_STOCK more specifically (only for the buyer's closest or selected location)?
Defer precise location-to-region mapping to RAOS-0003. RAOS-0005 surfaces the full perLocation array and the CONDITION code; 0003 refines it with location feasibility. This keeps 0005 independent of fulfillment logic.
#2Reservation state as pipeline input vs external hold-tracker: is the module-level holds slot the right shim?
The evaluator uses setInventoryHolds() to inject hold state before evaluateOffer(). This works but is an awkward shim. Should the pipeline support an "evaluation context" bag that extensions can read without needing module-level state?
The shim is intentional and documented. The pipeline could grow a `sideInputs` slot in a future RAOS-0000 revision. Until then, the holds-slot pattern is the minimal approach. The determinism invariant is preserved: the holds are set immediately before evaluateOffer() and cleared after use.
#3Should preorder items be blocked (not addable) or conditionally addable?
Currently PREORDER_NOT_YET_BUYABLE is CONDITION severity — the item is visible and the reason is surfaced, but it's not blocked. A merchant may want to accept preorders (add to cart OK) vs not (block until release). The spec doesn't differentiate.
The agent interprets CONDITION as "resolvable" — a merchant implementing preorder acceptance would mark it as such via intent-capture (RAOS-0013 forward-ref). Open for comment on whether a more explicit "preorder_enabled" flag belongs in the config.
#4Oversell race: should the spec define an atomic reservation protocol?
Two agents grabbing the last unit is a real problem. The spec documents this as a checkout-time concern with reservation TTL as mitigation. But should RAOS-0005 define a more explicit "claim" step (vs "soft hold") where the checkout system atomically decrements and confirms?
The atomic stock decrement is intentionally in the checkout system (RAOS-0012). RAOS-0005 surfaces the re-evaluation signal (RESERVATION_EXPIRED) and the TTL as the agent-facing mitigation. The checkout claim protocol is a RAOS-0012 concern.
Have a view on any of these?
The oversell race and location-mapping questions are the hardest. If you operate a multi-location grocery or run a BOPIS integration, your edge cases are exactly what this spec needs.
rikbanerjee007@gmail.comWhy start here
Inventory truth is must-have #2 in the program (after quote integrity). An agent that confidently sells an out-of-stock item destroys buyer trust faster than any other failure mode. The freshness TTL makes this spec load-bearing for RAOS-0007 (Quote Integrity): a quote token is only as trustworthy as its inventory signal, and a stale inventory at quote-time is a liability. Shipping this before 0007 means 0007 can bind ComputedAvailability directly.