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 (0)