The model proposes a plan, but it does not freely choose the command shape. Metnos gives it a restricted executor pool and, when the model service supports GBNF, constrains decoding to the Framework structure and admitted names. Parsing, validation, and Vaglio then inspect the result before anything runs.
Ask Metnos with a request such as:
Find the PDFs modified this week in the Projects folder, compute each file's SHA-256 digest, and show me the result.
The planner must not answer with a vague explanation. It must produce a structure like this, restricted to names in the pool computed for the turn:
{
"steps": [
{"tool": "find_files", "args": {"path": "Projects", "extension": "pdf"}},
{"tool": "compute_signatures", "args": {"algorithm": "sha256"}}
]
}
This example illustrates shape; it neither authorises paths nor replaces argument resolution. Relative dates, references between steps, and filesystem scope are resolved and checked by later layers.
| Level | Status and role |
|---|---|
build_framework_grammar | This is the ordinary path when METNOS_PROPOSER_GRAMMAR=1, which is the default. It constrains Framework shape and the tool field to the effective pool; final_answer is included as a valid terminal. |
build_framework_grammar_typed | With METNOS_PROPOSER_GRAMMAR_ARGS=1, it also binds each name to its argument schema. Arguments marked runtime_resolved are excluded because they belong to the runtime. This mode is not the default. |
runtime/tool_grammar.py contains the JSON Schema-to-GBNF
translator, call validation, and semantic filters reused by the engine. The
generate_tool_grammar API generates a single
tool_call; the planner instead uses the Framework grammar, which
may contain several steps.
Natural-language signals—such as undo, recurrence, proximity, skills, and provider names—come from the translatable detection lexicon. A new language requires lexical data for that language, not conditions hard-coded for individual phrases.
When argument constraints are enabled, every step has its own branch: the
name compute_signatures can only be followed by arguments derived
from its schema. Closed enums become GBNF alternatives; strings, numbers,
booleans, arrays, and nested objects become their corresponding primitives. If
a schema cannot be typed, that branch uses free JSON arguments and leaves
validation to the executor.
| Grammar guarantees | Grammar does not guarantee |
|---|---|
| A JSON structure admitted by the generated profile. | That the plan truly satisfies the user's request. |
| On the ordinary path, executor names that belong to the turn's pool. | That the user authorised the action or named path. |
| In typed mode, name-to-argument-schema correspondence for translatable branches. | Postconditions, real effects, service availability, or data correctness. |
| Output that is easier to parse and reject explicitly. | Absolute reproducibility of language reasoning or free-form content. |
Schema translation has a depth limit. Beyond it, or for complex constructs, the generator falls back to generic JSON. Post-decoding validation checks the top-level shape, name, required arguments, and synthetic terminals; deep validation remains the responsibility of the executor and the common contract.
Grammar construction observes several requirements of the current local model service:
tools field;If a service does not accept the grammar parameter, the switch
to grammar-free mode is logged. The plan must still pass parsing and
validation, but no longer benefits from the decoding-time constraint.
Given the same ordered pool and schemas, the generated GBNF string is deterministic. This makes the model-visible surface comparable and prevents an absent name from appearing on the constrained path. It does not make the model's whole response deterministic: free text, semantic values, and new plans still come from probabilistic inference.
The grammar is built in memory and makes no network call. Its main cost is during decoding, when the service removes inadmissible tokens at each step. Metnos limits that cost by building grammar for the turn's pool rather than the whole catalog, and gives the proposer an output budget derived from request complexity.
The documentation does not freeze a test count that would quickly become obsolete. The main suites are:
tests/runtime/engine/test_tool_grammar.py tests/runtime/engine/test_grammar_args_typed.py tests/runtime/engine/test_routing_pool.py tests/runtime/engine/test_tool_call_parser.py
They cover deterministic generation, name-to-argument unions, nested schemas, closed enums, intent and provider filters, synthetic terminals, duplicate-rule removal, and truncated-output recovery. Engine tests also verify that a plan cannot name executors excluded from the pool.
GBNF is the mechanism used by the llama.cpp-compatible local service. Other services may offer constrained JSON Schema or native tool calling. The Metnos contract does not depend on a provider's commercial name: an adapter is suitable only if it states precisely which constraint level it enforces and returns data that the common parser and validators can inspect.
Model virtualization explains how logical roles map to configured services. Changing a service must not change the executor vocabulary, user authority, or Vaglio rules.
runtime/engine/grammar_framework.py — Framework grammar and its typed-argument variant;runtime/tool_grammar.py — schema translation, validation, and deterministic filters;runtime/engine/proposer.py — mode selection and model invocation;runtime/llm_provider.py — grammar transport and model-service response parsing.