This is a submission for the Sanity Challenge, Path One: Ship an Agent That Queries Real Content
What I Built
Ultra Bridge is an agent for screen reader users. You ask it things like "What is the NVDA equivalent of JAWS Insert+F7?" or "Which keys do different jobs in JAWS and NVDA?", and it answers from a Sanity dataset through Sanity Context MCP. Every answer says how well the underlying command is verified, so you know whether to trust it or check it yourself first.
I built it because I rely on a screen reader myself, and moving between JAWS and NVDA means relearning keys that look the same but are not. That gap costs real time and real confidence, and it is exactly the kind of problem structured content is good at closing. Ultra Bridge is part of the Ultra suite of accessible software I am building.
The reason it needs structured content: a keyword search can find a page that mentions both screen readers, but it cannot tell you which keys collide, which ones translate cleanly, or which ones simply do not exist yet on the other side. In Ultra Bridge an action ("show a list of all links") is separate from a command (the keys one screen reader uses for that action). Equivalents are found through the shared action, so a relationship question becomes one query instead of a guess.
This query lists every single letter key that exists in both screen readers but does a different job:
*[_type=="command" && screenReader->slug.current=="jaws" && context=="browse-mode"]{
keys,
"jawsAction": action->title,
"nvdaMeansSomethingElse": *[_type=="command" && screenReader->slug.current=="nvda" && keys==^.keys && action._ref != ^.action._ref]{"action": action->title}
}[count(nvdaMeansSomethingElse)>0]
Against the seed data it returns six keys. R is "region" in JAWS and "radio button" in NVDA. Q is "main content" in JAWS and "block quote" in NVDA. A, O, S and D collide too. That is exactly the kind of thing that costs a person time when they switch, and exactly the kind of question a keyword search cannot answer.
Demo
https://ultra-bridge-xm71.vercel.app/
The page has example questions you can press. Open "How this was found" under any answer to see the exact GROQ query the agent ran to get there — nothing is hidden between the question and the data.
Code
https://github.com/demirajvazi10-max/Ultra-Bridge
How I Used Sanity
Schema. Seven document types: screenReader, source, action, command, concept, difference and guide. The dataset currently has 229 documents: 60 actions, 127 commands, 13 concepts, 12 differences, 7 guides and 8 sources.
Trust as data, not as an afterthought. Every command has a verification field (tested-by-author, cross-checked, first-party, third-party, recalled) and references to source documents. The system prompt tells the agent to say the verification level out loud whenever it uses a command, and to point to input help (Insert+1 in JAWS, NVDA+1 in NVDA) whenever a command is not yet confirmed. When two sources disagree, the disagreement is written on the command instead of hidden. Right now 11 commands are tested-by-author, 58 cross-checked, 44 first-party and 14 third-party — zero are still sitting at the weakest "recalled" level, because I keep a running checklist of what still needs a hands-on pass on a real screen reader.
Sanity Context, two modes.
- GROQ mode serves the structured dataset. The agent fetches
/initial-contextover HTTP, puts it in its system prompt, and usesgroq_queryandschema_explorerfor anything that names a specific command, key or action. - Knowledge Base mode serves seven guides written in prose, reserved for open-ended questions like "what should I learn first" or "how are the two screen readers different overall," where a single structured query would not be the right shape of answer. The Knowledge Base is created and built from the CLI (
sanity context create,imports create,build --watch).
The two initial_context tools have the same name, so the agent drops both and inlines their content directly into the system prompt instead. If the Knowledge Base endpoint is unavailable, the agent falls back to the structured dataset only rather than failing the whole request.
Engineering for free-tier reality. The public demo runs on free model tiers, which cap tokens per minute hard enough that a naive integration falls over under real traffic. So the agent only exposes the two tools it actually needs (groq_query, knowledge_base_read), compacts every tool result before it goes back to the model (stripping fields the model does not need and hard-capping length), and chains four model providers (Google, Groq, Cerebras, Anthropic) so a rate limit on one provider fails over to the next instead of failing the request. The public page also rate-limits per visitor.
What I learned.
- One endpoint serves one source type, so the structured data and the Knowledge Base need separate access paths.
- GROQ
matchis tokenised text matching, so I use equality (==) for keys that contain+, or the match would silently split "Insert+F7" into pieces. - The Sanity CLI covers almost everything I needed: project, schema deploy, dataset import, tokens and the Knowledge Base. That mattered because I work from the command line.
- Because Sanity's API was not reachable from where I built the first version, I wrote a small local server that speaks the Context MCP tool names and evaluates real GROQ with
groq-js. It let me test the agent, the prompt queries and the whole interface offline, and it ships in the repo so anyone can try the project without accounts.
Accessibility. I am blind and use JAWS day to day, and I tested this myself before anyone else touched it. The interface has a skip link, real headings for every question and answer, one polite live region that announces the full answer once when it is complete, a "read last answer again" button, and stable focus in the question box. An end-to-end test drives it in Chromium and runs axe-core in light and dark colour schemes, so accessibility is checked automatically, not just by hand.
Sanity Project Details
Project ID: u2f7mbu5
Dataset: production (public)
Top comments (0)