Build a Domain Glossary
Walk a codebase and produce a domain glossary: the ubiquitous-language layer of its docs, in the domain-driven-design sense. The glossary gives the team and the agents one shared language for the domain, keeps naming consistent so one concept wears one name, and anchors agent context in domain terms.
It is reference material in the Diataxis sense -- neutral, factual, built for quick lookup, no narrative. It is a single compact file: a flat, grouped list of terse one-line entries, small enough to load into agent context and complete enough to be the human reference. One file serves both roles -- it does not split into a verbose "full" file plus a hand-written compact "agent" summary.
The one split the skill does make is mechanical, not editorial. Once a glossary
grows too large to load on every run, a names-only index (GLOSSARY-INDEX.md)
sits beside it -- a flat list of every headword, one per line, nothing else --
and AGENTS.md loads that index instead of the full file, which moves to
grep-on-demand. The index is the headwords verbatim, never a reworded digest.
See "The term index" below; a small glossary needs none.
This skill is the glossary counterpart to an index skill. An index is a map -- it points at where things live. A glossary is a dictionary -- it says what the domain words mean. They share a spine (modes, working folder, parallel discovery, verification-then-report) but produce different artifacts.
A glossary, not a data dictionary
Each entry defines what the term is and stops there. The glossary states meaning; the code states representation and usage. It does not record the type that carries the concept, the field width, the per-message field paths, the modules a term appears in, or how the term is used elsewhere -- those churn, drift, and duplicate what the code already owns.
The litmus test: symbol is "an alias that uniquely represents a tradable
product," not "traded instrument; an order book holds one symbol." The good
version captures the essence -- an alias, uniqueness, a tradable product -- and
stops. The bad version drags in the order book; that fact belongs to the
definition of order book, not symbol. Otherwise every term ends up
redescribing every place that uses it, and the glossary has infinite surface
area.
Define the concept, not the mechanism
Define a term -- and write the framing paragraph -- by what it is and what it is for, not by how it works. Do not describe the steps, the order of operations, the flow, or the components an input passes through. State the concept's purpose and the capabilities it provides, and stop.
Three reasons this matters:
- Mechanics drift and are often configurable, so a flow description is fragile and quickly wrong.
- Describing mechanics invites factual errors: the more operational detail a one-line definition claims, the more chances it has to be wrong.
- A lookup wants meaning, not implementation. The reader who needs the mechanism reads the code; the glossary gives them the concept.
Framing paragraph: capabilities, not a pipeline trace. Bad, tracing a configurable pipeline: "...accepts normalized order requests, routes them through a protocol-agnostic pipeline to venue adapters, checks them against per-account risk limits before they leave, and tracks the order lifecycle, positions, and P&L." The routing pipeline is configurable -- stages such as the firewall may be skipped -- so the flow is not even stable. Good, naming capabilities: "...provides normalization of inbound and outbound messages, hierarchical pre-trade risk checks, and order-lifecycle, position, and P&L tracking."
Entry: the concept, not the steps. Bad (mechanism, and factually wrong -- the firewall does not amend):
**Firewall**\
The pre-trade risk component that accepts, amends, or rejects each order request
before it reaches the venue.
Good (what it is and does, conceptually):
**Firewall**\
The pre-trade risk component that evaluates an order against configured risk
limits and either allows or rejects it.
First step: identify the domain (and subdomain)
Before any term work, establish what the application's domain is. Read
README.md, docs/, and AGENTS.md where present to determine the field the
system operates in -- trading, clinical care, logistics, audio, quantitative
research, data reconciliation, and so on -- and, in a larger multi-module repo,
the subdomain each module occupies.
This step is load-bearing for everything after it:
- the domain decides which authoritative external glossaries to consult;
- the subdomain decides which vocabulary each module owns;
- the domain and subdomain labels become the
Domain context:line and the framing sentence in the assembled glossary.
Do not start surfacing terms until the domain is named. Record the identified domain and subdomains as a short label list at the top of the working notes so later phases share the framing and the assembly phase can lift them verbatim into the header.
What the skill produces
A doc tree parallel to the README tree (orientation prose) and any INDEX tree (navigation map):
- Root
GLOSSARY.mdat the project root -- the application-wide glossary: every concept shared across subdomains, stated once. This is almost always the whole output. - Per-subdomain
<module-path>/GLOSSARY.md-- only when a module has earned one: it owns vocabulary genuinely local to that subdomain, which the root should not absorb. Per-subdomain files back-link to the root. Do not create empty module files; bias hard toward the single root file. GLOSSARY-INDEX.md-- only when its sibling glossary is too large to load on every run. A flat list of every headword in the glossary it sits beside, one per line, no definitions and no prose. It sits next to that glossary (the root index beside the rootGLOSSARY.md; a per-subdomain glossary that grows large earns its own sibling index), and indexes only that file's terms.AGENTS.mdloads the index in place of the full glossary, and agents grep the full file for a definition. A small glossary needs no index -- the full file is already the compact loadable artifact. See "The term index".
The two levels form one hierarchy with a single allocation rule: a term shared across subdomains lives at the root and is stated once there; a term only one subdomain uses, and that a reader of another subdomain would never need, lives in that subdomain's file. When in doubt, promote to the root -- one source of truth beats a term duplicated across module files. A subdomain file never redefines a root term; it adds only what is local to it. Most repos are a single root file; a per-subdomain file is the exception a large, genuinely partitioned domain earns.
It also writes a sibling GLOSSARY-report.md at the project root for the
human reviewer: ambiguous terms, cross-boundary name divergences worth a
unify-or-keep decision (with the concrete code-name mapping the glossary body
deliberately omits), README/code conflicts about what a term means, terms whose
definition leaned on an external glossary (with the source), and anything
deliberately left out. The glossary is for downstream agents and humans alike;
the report is for the human.
When the project wires docs into agents via AGENTS.md, the import points at
GLOSSARY.md directly for a small glossary. For a large one it points at
GLOSSARY-INDEX.md instead, and GLOSSARY.md becomes on-demand (grep a
headword). Either way there is no hand-written agent-only summary: the index,
when it exists, is the headwords verbatim, not a reworded digest. How the index
is used and kept in sync is stated in AGENTS.md, not in the index file -- the
file is the term list and nothing more.
What it is for, and what it is not
One source of truth per concern. The glossary defines concepts; it does not
restate what other artifacts own. It is not the rulebook (AGENTS.md), not the
rationale (ADRs), not the orientation prose (README), not the navigation map
(INDEX), and not a per-field data dictionary with types, constraints, and
storage detail. An entry may, in the report, point to the ADR that explains a
translation; it does not reproduce it.
This skill does not:
- write ADRs, READMEs, or design docs;
- invent definitions or conventions;
- restate rationale;
- document foundation/tooling libraries that are out of scope;
- rename code (it surfaces naming questions; the human decides).
It may update AGENTS.md only to wire the glossary into agent context: load the
always-loaded layer, and state the rule that vocabulary changes update it. It
surfaces and defines domain terms -- nothing else.
The term index
A small glossary is its own loadable artifact: the full file is compact enough
to ride in every agent session. A large one is not -- a few hundred entries cost
real tokens on every run, most of them unread on any given task. When a glossary
crosses that line (roughly 120-plus entries, or a rendered file past a few
hundred lines / a few thousand tokens -- judge against the project's always-load
budget, not a hard count), write a GLOSSARY-INDEX.md beside it and let
AGENTS.md load the index in place of the full file, which becomes
grep-on-demand.
The index is nothing but a flat list of headwords, one per line, in the same
order as the glossary. No framing paragraph, no Domain context: line, no
grouping, no usage note -- those either duplicate the glossary or belong in the
agent instructions that load the file. A reader (or grep) has the exact spelling
from a line, then greps that headword in the glossary for the definition.
Each index sits next to the glossary it indexes and lists only that file's
terms. The root index covers the root GLOSSARY.md; a per-subdomain glossary
that is itself large enough earns its own sibling index over its own terms. An
index never reaches into another glossary's vocabulary.
The index is mechanically derived, not authored: every line is a headword copied
verbatim from the sibling glossary -- including any parenthetical, so grep
matches (BOE (Binary Order Entry), not BOE). It holds no term the glossary
does not define and omits none that it does: the index term set equals the
glossary headword set, exactly. Regenerate the index in the same run that
writes or splices the glossary, in every mode, so the two never drift. The how
(grep a headword for a definition) and the sync rule live in AGENTS.md, not in
the index file.
Scope
- Domain modules only. Foundation and vocabulary libraries (generic
containers, logging, an event loop, a result type, build tooling) are
infrastructure, not domain, and stay out. Detect domain modules from the
project layout (subdirectories of
src/, top-level packages, the structureAGENTS.md/ ADRs describe). Let the user scope explicitly when the split is not obvious. - Domain-level, version-independent terms. A term belongs to the domain, not
a version or an implementation variant. Where a repo keeps versioned
vN/folders, benchmark variants, or generated code, those are implementation detail: aGLOSSARY.mdlives at the module root (or the project root), never under avN/folder. The term is version-stable.
How meaning is derived
Two derivation sources feed every definition. The codebase says which terms exist and how this repo wires them; the domain says what each term actually means. A definition grounded only in the code is self-consistent but may drift from the field; a definition grounded only in the field may not match what this repo built. Use both.
1. The codebase
Surface terms from these source kinds, in priority order. The kinds are language-agnostic; the concrete files differ per language. Read the bullets for your language and treat the rest as analogues.
- Type and vocabulary definitions -- where the language declares the
domain's named types, enums, and constants. These name the primary terms, and
enum values are part of the term -- give them inline.
- C++:
types.hppper domain,enum/enum class, strong-typed wrappers. - Python: dataclasses,
Enumsubclasses, Pydantic models,NewType,Literal/TypeAlias,TypedDict, SQLAlchemy/ORM models, dbtschema.yml. - TypeScript:
type/interface, string-literal unions,enum, Zod schemas.
- C++:
- Message / event / schema / record definitions -- field names are concept
usages and the place a cross-boundary rename shows up.
- C++:
messages.hpp/events.hpp, state structs. - Python: request/response models, table columns, protobuf/avro/JSON schemas, API response shapes.
- C++:
- Boundary and translation code -- functions that convert one domain
representation into another (constructors, mappers, adapters, ETL transforms,
serializers, e.g. a
make_order_stateor aproject*projector). These are the authoritative record of which upstream concept maps to which downstream name. Read the implementation in full, not just signatures -- a rename lives in the body. - Module READMEs and docstrings -- prose definitions to compress. When a README claim contradicts the code, resolve intent from the code, write the present-reality definition, and record the conflict in the report with both sides quoted.
2. The domain and its authoritative glossaries
Once the domain is identified, launch research agents to look up the recognized glossaries for that field and align each definition with the established industry meaning, so the glossary is consistent with the field, not merely with itself.
- Trading: NASDAQ, CME Group, the FIX protocol field dictionary (FIX is the authority for quantity and price terms -- OrderQty, LeavesQty, LastQty, LastPx).
- Clinical: SNOMED CT, LOINC, specialty bodies.
- Logistics: Incoterms, carrier standards.
- Other fields have their own authorities; find them.
Prefer the authoritative phrasing; aim for the terseness of NASDAQ's definition of a symbol: "an abbreviation assigned to a security for trading purposes." Record in the report which terms leaned on an external source and which source. A term genuinely local to the repo (an internal event type, a project-specific pipeline stage) will not appear in any external glossary; define it from the codebase.
Decompose composite terms
A composite term implies its components, and each component is its own entry. "Aggressor side" requires both "aggressor" and "side"; "best bid" requires "bid". Surface the atomic vocabulary, not only the compound, so a reader can look up the part they do not know. When surfacing terms, split every multi-word concept into its parts and check each part has an entry.
Surface domain acronyms and proper names
Protocol and standard names, venue and feed codes, and vendor or component
shorthand are first-class terms -- they are exactly what a newcomer cannot
decode. Surface them, expand the abbreviation, and define it conceptually.
Trading examples: FIX, SBE, BOE, UMDF (protocols and message encodings),
B3 (a venue), and product or component codenames. Keep their canonical casing
(FIX, not fix) -- the proper-casing rule for headwords never lowercases an
acronym or a proper name.
Inclusion filter: a glossary, not an English dictionary
Include a term only when its domain meaning diverges enough from plain English to
warrant an entry. Keep genuine jargon, acronyms, and precise domain terms
(aggressor, leaves quantity, FIX). Drop words used in their ordinary
English sense, even when they appear in identifiers: a tie meaning "two
securities sharing the same score" and an untie step that "perturbs tied
scores" are ordinary English (and untie is mechanism, not a concept) -- leave
them out. This also drops terms whose meaning is just the everyday technical word
with no domain-specific twist: in a protocol glossary, logon and logout say
exactly what they are -- omit them. The test: would a competent newcomer to the
domain, already fluent in English, have to look this up? If not, it is not a
glossary entry.
Entry schema
Each entry is a headword on its own line, then its essential definition on the next line, with a blank line between entries:
**Term**\
Essential definition.
**Next term**\
Essential definition.
The trailing backslash is a Markdown hard line break, so the headword renders on its own line above the definition.
- headword -- the concept in plain-English words, spaced, not a type name
(
engine event, notengine_event). Use proper casing: capitalize the first word, and keep acronyms and proper names in their canonical casing wherever they appear (Order type,Best bid,FIX session,B3 connectivity,SBE). One entry per concept; do not give a concept two names (no "user / user_id"). - essential definition -- one sentence stating what the term is, derived from the codebase and aligned with the domain's authoritative glossary. It is self-contained and conceptual: it defines the term itself, by what it is and what it is for -- not where it is used, what consumes it, or the steps by which it is mechanically processed (see "Define the concept, not the mechanism").
There are no anchor links, no related field, no not to be confused with
field, and no "carried by" field; the compact list carries definitions only.
Representation is deliberately omitted.
Be concise
The always-loaded layer -- the full GLOSSARY.md for a small glossary, or
GLOSSARY-INDEX.md for a large one -- rides in agent context on every run, so
every word costs tokens on every load. Keep each definition to the concept
itself, in as few words as carry the meaning; a tighter definition also keeps
the on-demand grep'd entry cheap.
Do not enumerate. A definition states what the term is, not the full set of values it can take or fields it contains. Name the concept; add one or two illustrative examples only when they aid understanding and the definition is not already long, never the whole list. The glossary is the map, not the terrain: it points at the concept, it does not reproduce the value set the code already holds.
- Bad (lists every value): "how long an order stays in effect: a day order lasts until the end of the session, an immediate-or-cancel order fills what it can at once and cancels any remainder, a fill-or-kill order must fill in full or cancel, good-till-cross rests until it would cross, ..."
- Good (the concept): "how long an order remains active before it expires."
- Bad (lists every field): "the static record of a security: symbol, asset, security type and subtype, contract multiplier, tick size, and venue."
- Good: "a security's static descriptive record, such as its symbol and tick size."
The same restraint applies to mechanism and components (see "Define the concept, not the mechanism"): "a control that stops an account's orders from trading against each other" needs neither the id that identifies it nor the cancel-instruction that governs it.
Cross-boundary naming differences do not appear in the glossary body. The glossary defines each concept once under its canonical name; renames and inconsistencies go in the report.
Ordering
Sort entries alphabetically by term. A glossary is a lookup reference: a reader who knows the word but not the meaning scans for it, and alphabetical order is the only ordering they can predict without reading the whole file. Do not order by concept, by pipeline stage, or by the order terms were discovered. When the glossary is grouped (see below), sort alphabetically within each group.
Grouping
Do not group by default. A small glossary is a single flat list. Introduce
## groups only when the term count is large enough that coarse buckets aid
lookup, and keep any buckets coarse: if a term plausibly belongs in two groups,
the groups are too fine -- collapse them. A calibration-sized glossary uses no
groups at all.
Divergence handling
The glossary body keeps one essential definition per concept and carries no
naming table. Cross-boundary naming differences are recorded in
GLOSSARY-report.md:
- Deliberate boundary rename (e.g. the execution price is the engine's trade price and prints on the public feed in FIX vocabulary as the last price) -> the glossary defines the concept once under its canonical name; the report records the concrete code-name mapping.
- A name unified across the boundary -> a single concept, one entry, nothing special to say. Do not invent a divergence for a name the domains share.
- Accidental divergence -> define the concept once under its canonical name, and flag it in the report as a unify-or-keep decision. The skill does not rename code; it surfaces the choice.
The skill cannot always tell deliberate from accidental. Default to defining the concept once and raising the naming question in the report when intent is unclear.
Output formats
See references/output-template.md for a complete worked example -- the root
file, an earned per-subdomain file, the large-glossary GLOSSARY-INDEX.md with
its alternate AGENTS.md wiring, and the report, against a realistic layout. The
shapes in brief:
Root GLOSSARY.md:
# <System or domain name>
<one short framing paragraph: what the system is, at a high level>
Domain context: <domain and subdomain labels>.
**Term**\
Essential definition.
**Next term**\
Essential definition.
- Title names the system or domain (
# Matching Engine, not# GLOSSARY-- the file is a glossary, but its heading names the subject). - Framing paragraph reads like a README's opening sentence: a high-level description of what the system is, naming the major capabilities at the level a newcomer needs to place the terms. It does not trace the pipeline or data flow, describe the architecture, explain what the glossary file is, or name source libraries, folder layout, or the build/lab structure -- those are too specific and drift (see "Define the concept, not the mechanism").
Domain context:line lists the domain and subdomain labels established in the identify-the-domain phase, grounding the framing in the same terms.- Then the entries, as a flat list. No per-entry fields, no links, no cross-domain-naming section, no maintenance section, and no groups unless the term count makes coarse buckets worthwhile.
Per-subdomain GLOSSARY.md (only when earned): a title naming the subdomain,
a framing paragraph that says what the subdomain is at a high level the same way
the root frames the whole system, a back-link to the root
(Part of the [project glossary](../../GLOSSARY.md). -- adjust the relative depth
to the module's actual location), and the subdomain's genuinely local terms in
the same compact form.
GLOSSARY-INDEX.md (only when its sibling glossary is too large to
always-load): a flat list of the sibling glossary's headwords, one per line, in
the same order as the glossary, copied verbatim. No title, no framing, no
Domain context: line, no grouping, no usage note -- just the terms.
<Term>
<Next term>
<Next term>
...
GLOSSARY-report.md: grouped by category -- ambiguities, divergences
(unify-or-keep, with the concrete cross-boundary name mapping), README/code
conflicts (both sides quoted), external-source notes (term -> glossary used),
decisions, and skipped. Keep empty sections with a "none" note so the human
knows the run checked.
AGENTS.md integration: when the project has AGENTS.md, wire in the
glossary. For a small glossary, add @GLOSSARY.md to the always-loaded context.
For a large one, add @GLOSSARY-INDEX.md to the always-loaded context instead,
list GLOSSARY.md under on-demand context with the grep-a-headword workflow, and
note that the index's term names ride in every session while definitions are
grepped on demand. Either way, state the sync rule: domain-vocabulary changes
update GLOSSARY.md (and, when an index exists, splice the matching headword
into or out of GLOSSARY-INDEX.md in the same change). Reference any
per-subdomain files as on-demand drill-downs.
Drafting follows the project's documentation conventions: have subagents consult
the documentation skill for structure and writing-clearly-and-concisely for
prose (active voice, no hedging, no filler). Plain text only -- no glyphs or
icons, including in any ASCII trees or tables.
The glossary is markdown read through a rendered view. Navigation uses what
markdown gives: ## theme headings for grouping and browser find for search. The
skill produces no separate HTML view and no generated site.
Modes and workflow
Three modes, with explicit user instruction taking precedence over project state:
- Full: no root
GLOSSARY.md, or the user asks to regenerate. Identify the domain, surface all terms, write the root file, any earned subdomain files, the report, and theAGENTS.mdwiring when present. - Scoped: the user names modules or a term set. Re-surface only those; splice the root only where entries changed.
- Incremental: root
GLOSSARY.mdexists; use git to find the vocabulary-bearing files changed since it was last committed (the type/enum definitions, the message/event/schema definitions, state structs, and the boundary translators -- a changed translator can introduce or retire a cross-boundary rename even when no type changed), derive the affected terms, and run scoped against them. Produce a changes report.
In every mode, when a GLOSSARY-INDEX.md exists -- or the glossary has grown
large enough to warrant one (see "The term index") -- regenerate or
splice it in the same run, so its headword set stays exactly equal to the
glossary's.
Phases
Parallelized one subagent per domain module (use dispatching-parallel-agents
when the source does not fit one context; dispatch all subagents in a single
message so they run concurrently):
- Working folder. Create
/tmp/glossary-<YYYYMMDD-HHMMSS>-<short-id>/and use it as the spine of phase-to-phase communication. Every phase writes its full output there; subagent return messages only compress what the agent learned. Every subagent prompt names the exact file the subagent must write, so assembly is a predictable glob. Suggested layout:
The folder is not cleaned on completion; final artifacts go to the repo (/tmp/glossary-<run-id>/ |-- manifest.md skill name, start time, args, project root, mode |-- run.log append-only log of phase events |-- domain.md identified domain + subdomain labels (load-bearing) |-- sources/<module>.md located vocabulary-bearing files + translators |-- research/<domain>.md authoritative-glossary findings |-- terms/<module>.md surfaced terms + draft definitions `-- report-fragments/<module>.mdGLOSSARY.md,GLOSSARY-report.md, any<module>/GLOSSARY.md, andGLOSSARY-INDEX.mdwhen the root glossary is large enough to warrant one). - Identify the domain and subdomains from
README.md,docs/, andAGENTS.md. Writedomain.md. Do not proceed until the domain is named. - Locate the vocabulary-bearing files and translators, and launch research agents to look up the domain's authoritative glossaries. These run in parallel -- code location and domain research do not depend on each other.
- Surface terms (decomposing composites) and write essential,
domain-aligned definitions. One subagent per module; each writes its terms
to
terms/<module>.mdand findings toreport-fragments/<module>.md. - Assemble the single compact root file (plus any earned subdomain files)
from the working folder, the report from the fragments, and the
AGENTS.mdwiring when present. For each glossary file large enough to warrant it, also assemble its siblingGLOSSARY-INDEX.md-- a flat list of that file's headwords, one per line, term set identical to it -- and point theAGENTS.mdalways-loaded import at the index instead of the full file. - Verify (below), then stop and report.
Verification
Before declaring done, check:
- the identified domain is recorded;
- the root file is titled with the system/domain name (not
GLOSSARY), opens with a high-level framing paragraph that says what the system is (not the pipeline, the architecture, the glossary file, or the build/lab structure) and aDomain context:line whose labels match the identified domain and subdomains, and the framing names no source libraries or folder layout; - the framing paragraph names the system's capabilities conceptually and does not trace the data flow or pipeline (no "routes X through Y to Z" description);
- every per-module file opens the same way for its subdomain, back-links to the root, and corresponds to a real module;
- no empty sections;
- the root states every shared concept exactly once;
- entries use the headword-on-its-own-line format (a bolded headword, a hard line break, then the definition), with proper casing and acronyms/proper names in canonical casing;
- entries are sorted alphabetically by term (within each group, when grouped);
- every composite term's components have their own entries;
- domain acronyms and proper names are surfaced, expanded, and defined;
- no entry is a plain-English word used in its ordinary sense (the glossary is not an English dictionary);
- each definition is self-contained and conceptual: it defines what the term is and is for, not its consumers, and not the steps, flow, or order of operations -- and makes no claim a different configuration would falsify;
- when a glossary is large enough to warrant an index, a
GLOSSARY-INDEX.mdsits beside it as a flat list of headwords, one per line, with no framing, grouping, or other prose, and its term set is exactly equal to that glossary's headword set -- no missing terms, no extra terms, no duplicates, no terms drawn from any other glossary; - index lines are verbatim copies of the headwords (parentheticals included), so a grep of a line matches in the sibling glossary;
AGENTS.md, when present, wires the glossary into agent context (the fullGLOSSARY.mdalways-loaded for a small glossary, orGLOSSARY-INDEX.mdalways-loaded withGLOSSARY.mdon-demand for a large one) and states the sync rule.
If verification fails, stop and report -- do not hand-wave. The glossary is consumed as ground truth by downstream agents.
Manual review gate
The skill drafts; the human reviews and edits before the glossary is treated as ground truth. State this explicitly when handing off: the report exists so the reviewer can confirm the ambiguities, divergences, and external-source calls the run made.
Reference files
references/output-template.md-- a complete worked example of the rootGLOSSARY.md, an earned per-subdomainGLOSSARY.md, theAGENTS.mdwiring,GLOSSARY-report.md, and the large-glossaryGLOSSARY-INDEX.mdwith its alternateAGENTS.mdwiring, so a subagent drafting the glossary has the target shape in front of it.