Concept 8 of 10

One shared vocabulary.

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.

The agent can't find your gate, so it skips 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.)

Define the term once; the model looks it up

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.

lexicon.jsonl
→ 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 model finds the right term by meaning, not spelling

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.

resolve.jsonl
→ 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.

gateway.yaml
embeddings:
  backend: ollama                               # none (default) | ollama | openai_compatible
  url: http://localhost:11434/api/embeddings    # local Ollama, or any provider
  model: nomic-embed-text

The agent hits the gate, not around it

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.

Concept 8 of 10

Next concept →

Compose & model-per-step at scale

Define a capability once, govern it everywhere, and pay only for the model each step needs.

See all concepts