Inspiration

Read any article about AI and you hit ten words nobody defined. Some of them the
author explains. Some they assume you already know. Some are there to sell you
something. You cannot tell which is which — and looking each one up gives you a
textbook definition that has nothing to do with how the word was just used at you.

You ask your AI. It tells you. Then it's gone, and the next person who reads the
same article asks the same question.

Existing AI glossaries are written top-down: an editor decides what a word means
and the entry sits there. But these words are not settled. "MCP" means one thing
in Anthropic's spec and something else in a vendor's brochure. A dictionary that
picks a winner is lying about the state of the language.

**nomos** (νόμος) is Plato's word for meaning that is agreed, not given. That is
the side this dictionary sits on.

What it does

ai-nomos is an AI-term dictionary with no input box. You don't type — you send
your AI.

Hand your agent a link and it reads the article itself, in your browser, using
the WebMCP tools this page registers. It finds the words you named, brings back
ones you didn't, and rules that some of yours aren't AI terms at all. Then you
decide whether any of it goes into the dictionary.

The dictionary never rules on what a term means. It collects how the term is
actually used — one record per term per document, each carrying the sentence it
came from and a link back. When eleven articles define MCP eleven different ways,
you see all eleven, split by the language the source was written in.

Current corpus: 133 plain-language entries, 1,325 sightings from 343 documents,
302 distinct terms — 1,217 Chinese, 101 English, 7 Japanese.

There is one write path and no editorial back door. The agent proposes, a person
confirms, and only then is anything stored. No entry can be edited into existence
by hand.

How we built it

**Five tools**, dual-registered on `document.modelContext` (ChatGPT desktop,
Chrome 152+) and `navigator.modelContext` (older spec drafts):

- `feedDocument({url, requested_terms?})` — read-only. Returns the extraction
  rules verbatim, a thin lexicon index, and the JSON Schema for a finding. The
  page never fetches the article; the agent does.
- `reportDocument` — read-only, called after the agent opens the article and
  before it extracts anything: title, byline, date, length, one-sentence gist.
  Nothing is stored. It exists because one tool call that takes a minute leaves
  the screen blank for a minute — splitting the task into stages is what makes
  progress real rather than a fake spinner. The title doubles as proof the agent
  actually reached the page.
- `submitFindings` — hands the findings to the page and answers `pending_review`.
  It does not write.
- `lookupTerm`, `trending` — read-only, the public read side. A second agent on
  any other site can ask this dictionary what a term currently looks like in the
  wild. It gets quotes and sources, never a ruling.

**One data contract** (`context/contract.md`, English, versioned) is the single
source of truth for page ↔ agent and page ↔ server. Changes go through a
`contract:` commit, never through conversation.

**Three server-side locks** every finding must pass — the sentence must contain
the term, the definition quote must be a verbatim substring of the context, and a
borderline term with no quote is dropped as noise. A Python reference
implementation and a JS implementation are diffed against the same fixtures
before every deploy, so the page and the server cannot drift apart.

Static HTML/CSS/JS, no framework, no build step. Vercel serverless for the write
path, Upstash Redis for sightings. Interface in Chinese, English and Japanese.

Challenges we ran into

**The spec has no page-side confirmation, so the page had to stop being the thing
that writes.** Our probe found no working confirmation API: `requestUserInteraction`
was removed from the WebMCP spec in PR #205 (June 2026, "not fully specified, no
implementations"), and ChatGPT's shim returns "requestUserInteraction is not
supported by the Codex WebMCP shim". Rather than pretend to gate, we moved the
write out of the tool call entirely: `submitFindings` returns `pending_review` and
holds the findings on the page; the person confirms and *the page* posts to
`/api/findings`. No tool call can write on its own.

**Order of work has to live where the agent reads first.** We put the sequence
(feedDocument → reportDocument → submitFindings) in the rules blob and the agent
skipped `reportDocument` in a live run. Moving it to the opening line of each tool
description *and* into a `next_step` field in the response body fixed it.

**An ungrounded model will happily invent a definition.** So the contract forbids
it: `definition_quote` must be a verbatim substring of the surrounding context,
and the server rejects the finding if it is not. The agent judges; the quote proves it.

**The server has to have the last word.** The page runs the same locks the server
does, so the agent gets instant feedback. But `PII_DETECTED` is server-only, and
the server's verdict is what the page displays — a finding can pass on the page
and still be rejected, with the reason code shown.

Accomplishments that we're proud of

**Tool descriptions are the product.** `reportDocument` exists purely so the human
sees evidence of progress while the agent reads. That is a UX decision expressed
entirely as a tool description — there is no UI for it, and no other way to build it.

**The agent is not a courier.** `submitFindings` explicitly instructs the agent to
disagree with the user: find each requested term and rule on it (a requested term
that is not an AI term still gets a finding, with `domain: not` — that is the
agent's verdict and the user sees it), and add terms the user missed. In our test
runs the agent added roughly as many terms as the user asked for.

**It still works without an agent.** WebMCP is not everywhere yet. With no
`modelContext` present, the page degrades to local matching: paste text, see which
of the 133 known terms are in it. The site is not a dead page for the 99% of
visitors without a flagged browser.

**Multilingual by construction.** `lang` is derived per source document, not per
term — an English term quoted in a Japanese article is a Japanese sighting. A term
page shows each language side separately, because a word is not used the same way
in two language communities.

What we learned

**A person and an agent can do something here that neither can do alone.**

The person brings judgment about *which article matters* and *which words bothered
them*. They cannot read 343 articles to see how a word is drifting.

The agent reads the article closely and applies the same rules every time. It has
no idea which article is worth reading, and left alone it will confidently define
a term from memory instead of from the page.

The dictionary holds the accumulation neither of them can — so that when you bring
article #344, the page can put its definition of "RAG" next to the other ways
other authors used it.

**Anti-spam is a limit, not a security feature.** An anonymous browser-held id and
a salted document hash deduplicate honest use. Neither stops a determined actor,
and we do not claim they do.

**What we verified, and what we did not.** Verified 2026-08-27: `registerTool`
works, tools execute, the agent channel is live in ChatGPT desktop (Work mode,
GPT-5.6 Terra). Chrome 152 exposes `document.modelContext` natively. Not available
anywhere we tested: page-side write confirmation. We report it as a spec gap and
designed around its absence rather than around a feature that does not exist.

What's next for ai-nomos

**Scale.** Reads are already shaped for growth — looking up a term is
`ZRANGE by_term:{key}`, both touching one index rather than the corpus. What
breaks first is honest and known: the home page loads every sighting into the
browser to compute its stats (661 KB at 1,325 sightings; ~49 MB at 100,000), the
sightings API caps at 200 rows unless asked for a wider window, and there is no
pagination anywhere. The fix is counters incremented on write instead of a full
scan on read. It is not built yet because at this size a full scan is simpler and
cannot go stale.

**More languages.** The corpus is 92% Chinese today because that is where the
feeding started. The architecture is already per-document-language; it needs
readers, not code.

**The read side as a public good.** `lookupTerm` and `trending` are already open
to any agent on any site. The interesting version of this dictionary is not people
visiting it — it is other agents asking it what a word currently means in the wild,
and getting evidence instead of a ruling.

Built With

Share this project:

Updates

Submission history