Concept 8 of 10
Your agent invents its own name for your gate, can't find the rule you wrote, and routes around it. A lexicon — a glossary the model can query — lets you define a term once, so the model resolves its own wording to yours and hits the gate instead of skipping it.
You named the gate ci_green. The agent reaches for "tests
passing," looks for a move by that name, doesn't find one — and routes
around your gate instead of through it. The rule you wrote is right
there; the model just used a different word for it, so it never lands on
it. That's not a glossary problem for your team. It's a control problem:
when the agent's wording and your names don't match, the agent can't
find the rule, so it ignores the rule. The fix is to define a term once
and let the model resolve its own phrasing to your term — by meaning,
not by spelling. (If you know
domain-driven design, this is a ubiquitous language — one
vocabulary the whole system agrees on.)
A lexicon entry is a defined term: a one-line summary, optional detail,
and the aliases you've heard the agent use. You define it through the
same two tools you already use to drive flowgate — write it with
flowgate.command, and the model reads it back with
flowgate.query instead of guessing. Your guards reference
the same term, so the gate and its name live in exactly one place.
→ flowgate.command {
"subject": "lexicon:ci_green",
"definition": {
"definition_short": "CI has run on this commit and reported all checks passing.",
"aliases": ["tests passing", "green build", "checks green"]
} }
→ flowgate.query { "subject": "lexicon:ci_green" }
← { "term": "ci_green",
"definition_short": "CI has run on this commit and reported all checks passing." }
The payoff is concrete: the model stops needing to know your exact
names. When it reaches for "proof bundle," flowgate resolves that to your
defined ci_green — by meaning, even when the words
are nothing alike. Resolution is tiered: exact match first, then alias,
then semantic (matched by meaning), then a last-ditch
fuzzy match. With the semantic tier on, it dominates — meaning beats
surface form — so a near-miss in wording still lands on the right gate.
→ flowgate.query { "query": "tests passing" } # the agent's wording, not yours
← { "match_kind": "semantic", # matched by meaning, not spelling
"term": "ci_green",
"definition_short": "CI has run on this commit and reported all checks passing." } It's opt-in. Semantic matching is off by default — exact
and alias matching work with nothing to set up. Turn it on only if you
want it, by pointing an embeddings block at a provider: a
local Ollama model, or any OpenAI-compatible endpoint. Your terms get
embedded once, at define time; from then on the model searches them by
meaning.
embeddings:
backend: ollama # none (default) | ollama | openai_compatible
url: http://localhost:11434/api/embeddings # local Ollama, or any provider
model: nomic-embed-text
Now the names line up. You write ci_green once; the agent
can call it "tests passing" all day and still land on your gate, because
the model resolves its own wording to your term by meaning. The rule you
wrote actually gets used — which is the whole point of writing a gate.
And it scales with you: every new term you define is one more place the
agent can no longer route around. Wiring flowgate into Claude Code or
Cursor? The same two tools carry the lexicon, so this works in every mode
— see how to run it.