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.
Here, grammar does not mean the rules of English or Italian. It is a formal schema that defines which character sequences the model may produce while building a plan.
Ask Metnos with a request such as:
Find the PDF files in the Projects folder, read them, and show me the path and SHA-256 digest of each file.
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": {"base_path": "~/Projects", "patterns": ["*.pdf"]}},
{"tool": "read_files", "args": {"from_step": 1, "parse": "auto"}},
{"tool": "final_answer", "args": {}}
]
}
This example illustrates shape; it neither authorises paths nor replaces
argument resolution. References between steps, paths, and filesystem scope are
resolved and checked by later layers. read_files actually computes
the content's SHA-256 digest; compute_signatures, by contrast,
handles command safety signatures and would be the wrong executor here.
| 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.
Metnos-specific constraints that are not part of JSON Schema, such as
requires_one_of, and automatic wiring between step results remain
the responsibility of validators and the runtime. Typed grammar does not claim
to replace them.
| 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.
The proposer uses the planner.grammar logical workload, currently
mapped to the wise tier. The model, provider, extended reasoning,
and generation budgets belong to the central tier configuration, not to the
grammar.
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.