Skip to Content

gtmesh config

Read and edit gtmesh.config.yaml one value at a time, the way git config edits git config. This is how a mesh’s structural law gets changed — the guides prescribe gtmesh config set|add|unset wherever they name a knob, because a write through the verb preserves your comments, re-validates the whole file, and rolls back if the edit would break it. A mistake fails at the command instead of surfacing later as a confusing plan.

gtmesh config get <path> gtmesh config list [--resolved] gtmesh config describe [path] gtmesh config clean [--dry-run] gtmesh config set <path> <value> [--dry-run] gtmesh config add <path> <value> [--at <n> | --before <match> | --after <match>] [--dry-run] gtmesh config unset <path> [--dry-run]

What it does

Every write is comment-preserving and write-then-validate: the edit is applied to the YAML, the file is re-parsed and validated, and on any failure it rolls back (the file is left untouched) and prints the error. Paths are dotted/indexed — e.g. taxonomy.markets[0], sections_map.

  • get / list — read the value in effect at a path, or dump the config as JSON. Read-only.
  • describe — what a path is: its type, whether it’s required, its default, its allowed values, what it does, and where to read more (see below).
  • clean — drop every value that already equals its schema default (see below).
  • set — set a scalar (or a JSON literal for objects/arrays) at a path.
  • add — append to an ordered array (e.g. a sections_map rule). Because order matters (first-match-wins), the placement flags control where it lands.
  • unset — remove a value you set.

The file holds decisions; the schema holds defaults

gtmesh.config.yaml is project-ownedgtmesh upgrade never rewrites it. Two keys are unconditionally required (project and taxonomy.sections); every other key you leave out resolves from the config schema at load. An absent key is therefore not an unset one — it’s one the engine still owns.

That split is what makes a default shippable. Change a default in an engine release and every mesh that never overrode it picks the new value up on the next gtmesh upgrade, automatically, with no migration; a mesh that did set it keeps its choice. Write the default into the file and you invert that — the value is pinned in a file nothing will ever update, and no migration can safely unpin it, because default_status: planned is byte-identical whether you chose it or the scaffold wrote it.

So: set what you decided, leave out what you didn’t.

config describe — what can I set?

Because the file carries only your decisions, it is no longer the menu. gtmesh config describe is: it renders the config schema — every settable path, its type, whether it’s required, its default, its allowed values, what it controls, and the docs page that explains the block.

gtmesh config describe # the top-level index — every block, one line of what it's for gtmesh config describe taxonomy # one block, opened up: the key and everything under it gtmesh config describe --json # the whole schema, machine-readable

With no argument you get the map — each top-level key with its state and how many paths sit under it. Name a path and you get that key plus its whole subtree:

$ gtmesh config describe taxonomy taxonomy (required) — object docs: https://gtmesh.pages.dev/guide/operate/tuning/ taxonomy.sections (required) — string[] at least 1 item(s) taxonomy.axes (optional, default: {}) — map<string, string[]> taxonomy.facets (optional, default: {}) — map<string, string[]> taxonomy.intents (optional, default: []) — string[] taxonomy.markets (optional, default: ["us"]) — string[] at least 1 item(s), each matches /^[a-z]{2}$/ Set one with `gtmesh config set <path> <value>`.

Two path forms you’ll see: page_types[] is the shape of each item of an array — a concrete index works too, so gtmesh config describe page_types[0].template lands on the same entry — and environments.* is the shape under any key you name, because those keys are your own vocabulary rather than the schema’s.

A path the schema doesn’t know fails with the nearest ancestor it does know and the paths under it, so a typo answers itself:

$ gtmesh config describe taxonomy.markts error: no config path `taxonomy.markts` — the nearest the schema knows is `taxonomy`. Paths under it: taxonomy.sections, taxonomy.axes, taxonomy.facets, taxonomy.intents, taxonomy.markets.

It reads the schema, not your project, so it works from anywhere — including before gtmesh init, when you’re deciding what the mesh should be.

This is the reference, and it can’t go stale. The output is derived from the same schema the engine validates against, so a knob that exists is listed and a knob that’s gone isn’t. It ships with the engine too — gtmesh upgrade improves the explanations for a mesh scaffolded a year ago, which a comment in your own config file could never do.

--json is the same tree as a flat, ordered document — { schema_version, path, docs, fields[] }, each field carrying path, type, required, default, enum, constraints, description, docs, and leaf. That’s the surface an agent traverses to answer “what can I set here?”, and it’s why there’s no separate JSON Schema file to keep in sync.

Seeing what’s actually in effect

Once the file stops being the whole truth, two reads answer “what am I running?”:

gtmesh config list --resolved # the EFFECTIVE tree, every schema default filled in gtmesh config get default_status # the value in effect, and whether it came from the default

Plain gtmesh config list still prints the file — the decisions you’ve written. gtmesh explain renders the same resolved structure for humans.

Editing a value the file doesn’t name

The write verbs read the same way:

  • add onto an array the file leaves out writes the default out first, then appends. gtmesh config add taxonomy.markets gb on a file with no markets key gives you [ us, gb ] — the list you were actually running, plus the market you added (and so the engine still derives market into identity_keys).
  • set pins a value, whether or not the key was there before.
  • unset removes a decision. On a key the file doesn’t name it reports the value in effect and points at config set, because there is no decision to take back.

config clean — reclaim the defaults an older scaffold wrote into your file

Meshes scaffolded by an earlier gtmesh init carry ~25 keys’ worth of defaults as explicit values. gtmesh config clean removes them:

gtmesh config clean --dry-run # the paths it would drop, plus the YAML diff gtmesh config clean # apply it

It is behavior-neutral by construction. A key is droppable exactly when removing it leaves config_hash byte-identical — absent, it resolves right back to the same value, so every downstream stage sees the same tree. The command tests each candidate that way, and then refuses to write at all if the resulting file’s hash has moved. A value you overrode fails that test, so it is never dropped.

The blocks a skill reads are kept, whatever the hash says. config_hash speaks for the engine, which resolves an absent key from the schema; an engine-owned skill opens the file, so a value that merely restates a default is still its input — dropping aeo.overall_threshold would leave review-gate without the gate’s pass bar. clean withholds aeo:, images:, rubrics: and discovery: and tells you it did.

It reports what it took, what it kept, and the hash that held:

dropping 12 value(s) already supplied by the schema default: kind = "non_commercial" default_status = "planned" identity.anchor = "head" ... kept 3 value(s) an engine-owned skill reads from this file: aeo.overall_threshold aeo.answer_block images.model (a skill reads the file, not the resolved config — the value is its input even at the default) config_hash stays 904e45e4c7b395432c2c9d14935ade86dfcccf236325b3adcfd0866e0c762bac

Run it once, commit the result, and gtmesh plan produces the identical plan it did before. Comments on surviving keys are preserved; a comment attached to a dropped key goes with it. Running it again drops nothing.

Doing nothing is safe. A default written into your file keeps working exactly as it does today — you simply stay pinned to that value, and a future engine release that changes the default won’t reach you.

Derived fields the engine owns (e.g. the market auto-joined into identity_keys) are rejected with a pointer at the real lever (taxonomy.markets). --dry-run prints the resulting YAML diff without writing.

A hand-written config reformats on its first write — once. Writing through config re-emits the YAML in the serializer’s own style, so the first config set on a config you wrote by hand can touch far more lines than the value you changed. Your comments all survive and the parsed config is identical apart from the key you set, and every write after that is a clean two-line diff, because the file is already in that style. Make that first one its own commit — a “normalize config formatting” commit — and the rest of your history stays readable. The scaffolded configs already ship in the serializer’s style, so a mesh from gtmesh init gets the clean diff from the very first write.

config_version — the one key the engine writes

gtmesh.config.yaml carries a top-level config_version: the format version of the file, stamped and advanced by gtmesh upgrade when an engine release changes the config’s shape. It’s the only key you don’t set yourself — leave it to upgrade and commit it with the rest of the file. It names the file’s shape rather than your mesh’s settings, so it’s excluded from the config_hash a plan reports, and config clean leaves it alone for the same reason.

Arguments & flags

OptionDescriptionDefault
[path](describe) the config path to describe — omit for the top-level indexthe whole schema
--resolved(list) print the effective tree, with every schema default filled inoff (prints the file)
--at <n>(add) insert at index N (0 = front)append
--before <match>(add) insert before the first element whose JSON contains this substring
--after <match>(add) insert after the first element whose JSON contains this substring
--dry-runprint the resulting YAML diff without writing (clean also lists the paths it would drop)off

The global options (--project, --json, --quiet) also apply. describe reads the schema rather than your project, so --project makes no difference to it.

Reads & writes

  • Consumes: gtmesh.config.yaml (describe consumes only the engine’s own schema).
  • Produces: an edited gtmesh.config.yaml (an input file — not the registry), comments and key order preserved.
  • Commits: nothing to the registry; plan/apply still gate what reaches it.

Examples

gtmesh config get project gtmesh config list --resolved gtmesh config describe gtmesh config describe classification gtmesh config describe links.up --json gtmesh config clean --dry-run gtmesh config set kind commercial_catalog gtmesh config add taxonomy.sections glossary gtmesh config add sections_map '{"when":{"match":" vs "},"then":{"section":"compare"}}' --before '"section":"glossary"' gtmesh config set scoring.commercial_bonus '{">=30 cpc": 1.4, "default": 1.0}' --dry-run gtmesh config set classification.default_intent informational gtmesh config unset classification.section_intent.glossary
  • reference — the same idea for a reference table: its columns, required/optional, and allowed values.
  • explain — a human-readable view of the config you’re editing.
  • Reference — the version-exact flag list.
Last updated on