This is a submission for the Sanity Challenge, Path One: Ship an Agent That Queries Real Content
What I Built
CabinClaim is a cabin-rules desk, not a travel blog.
A guest asks one question. The desk answers what is allowed in the cabin for this carrier and this travel date from dated ruleClaim documents in Sanity. It does not search the web at ask time.
This only works because the content is structured. Official pages stay live at the same time and disagree:
- TSA publishes a 100Wh spare-lithium cap and a cabin-only rule.
- United publishes cabin centimetres and a spare-count wording the TSA power-bank page does not repeat.
- A 120ml sunscreen bottle is "a liquid" on every page. Only a
liquid_mlfield can fail it.
Keyword search finds "allowed," "100Wh," and "carry-on" in one blob. CabinClaim stores each official sentence as its own claim: operator, value, appliesTo, effectiveFrom, sourceUrl, and the exact quote. GROQ keeps claims still in force. When two current claims conflict, both quotes stay on screen with their sources.
If you delete value, operator, or effectiveFrom, the desk refuses. That is the Path One test.
Demo
- Live desk (no login): https://cabinclaim.vercel.app
- Studio: https://cabinclaim.vercel.app/studio
- Context MCP:
https://api.sanity.io/v1/context/organizations/opm3ppvty/mcp/cabinclaim - Public claims (no token): current
ruleClaimdocuments
Judge question — leave this as-is, then click Ask:
United UA 934, SFO to LHR. I have a 99Wh Anker power bank, a second 30Wh spare, and 120ml sunscreen in a 56 x 36 x 23 cm bag. What is allowed in the cabin, and which official pages disagree?
Expected:
| Item | Verdict | Why search fails |
|---|---|---|
| 99Wh power bank | Allowed in cabin | Search cannot apply lithium_wh lte 100 |
| 30Wh spare | Allowed in cabin | Same watt-hour field |
| Two spares | Conflict — both quotes shown | United count vs TSA page with no two-spare cap |
| 120ml sunscreen | Not allowed |
liquid_ml lte 100, not the word "liquid" |
| 56 × 36 × 23 cm bag | Not allowed | United width 35cm and depth 22cm are fields |
No login is required.
Code
GitHub: https://github.com/python07070/cabinclaim
The pieces that make the desk refuse a keyword answer:
-
src/sanity/schemaTypes/carrier.tsandruleClaim.ts— one official sentence per document -
src/lib/queries.ts— current-claims GROQ by IATA and travel date -
src/lib/solveCabin.ts— refuse unless required fields exist -
src/lib/mcp.ts—initial_context, thenknowledge_base_read -
scripts/seed.mjs— dated quotes withobservedOn
Current-claims GROQ:
groq
*[_type == "ruleClaim"
&& (
carrier->iata == $iata
|| carrier->iata == "ANY"
|| !defined(carrier)
)
&& effectiveFrom <= $travelDate
&& (!defined(effectiveUntil) || effectiveUntil >= $travelDate)
]
Top comments (2)
The structured claims model here is the key insight. Airline cabin rules are a perfect case where keyword search fails not because it's imprecise but because the rules themselves are structurally ambiguous — "allowed" appears in documents that technically disagree, and you can only surface the conflict with typed fields.
The demo table tells the whole story: "Two spares" resolving as "Conflict — both quotes shown" with both sources is what a travel desk needs to actually do. Most retrieval implementations would either pick one or blend them into an answer that's wrong for someone.
The GROQ temporal query (effectiveFrom <= travelDate, effectiveUntil >= travelDate) applied per carrier is the right architecture for rules that change over time and don't have consistent versioning across sources. This is the pattern that makes the desk trustworthy at departure date, not just "at time of data load."
Refusing unless required fields exist (operator, value, effectiveFrom) is a validation that most agents skip. The desk refuses is stronger than "the desk hedges." Well done on the Sanity Challenge submission.
Thanks , that is exactly the failure I was trying to make visible. “Allowed” shows up on both pages, so search looks confident while the typed fields (spare count, watt-hours, dates) actually disagree.
The desk only stays honest if it refuses when operator, value, or effectiveFrom is missing, and if it keeps both quotes when two in-force claims conflict. I did not want a blended answer a passenger would take to the gate.
Path Two is the same idea on the editor side: Claim Bench. An agent can extract a claim. Only a person can stamp it approved.