Skip to Content
DocumentationConcepts & theoryThe GoToMesh mental models

The GoToMesh mental models

GoToMesh has a handful of ideas you have to hold in your head to drive it well. None of them is hard, but a few are subtle enough that people build the wrong picture and then fight the tool. This page lays out the six that matter, each as what it is / why it matters / how to drive it.

If a term is unfamiliar, the glossary defines it. For how the files themselves fit together — which one owns which fact, and how they flow into the registry — see How the pieces fit. The step-by-step “which file, in what order” lives in Lifecycle and Tuning — this page is the model, not the procedure.

The mesh’s link model — which page plays which role (pillar / supporting / landing / hub / glossary) and exactly where each internal link points — is codified in the Internal Linking Rulebook . It is the conceptual source of truth behind everything on this page; its gtmesh crosswalk maps every rule onto the engine’s role/pillar/spoke terms and the doctor checks that enforce them.


1. Site type (kind)

What it is

Every mesh declares one kind in gtmesh.config.yaml. It is one of three:

kindWhat it describes
non_commercialContent hubs discovered from search demand: hubs plus spokes (the bundled demo).
commercial_catalogMany product / category pages plus an order action; domain axes group them.
commercial_pagesA few core service / product landings that content funnels up into.

Why it matters

kind fixes the authority topology — the shape of how pages point at each other and which pages are the destinations. Which pages are conversion pages, which are feeders, and which are leaf spokes all follow from it. It is the very first thing the operator establishes — the first interview question — because the rest of the structure is built on top of the answer.

How to drive it

Set it once, early. With the bundled scaffold it is kind: non_commercial. When you run the operator skill after gtmesh init, it picks the kind for you (from the interview) and writes it into the config via gtmesh config. You generally do not flip kind later — it is a structural decision, not a knob. See the walkthrough for how the operator establishes it.


2. The authority layer

What it is

One field describes where a page sits in the authority topology:

  • rolepillar, hub, sub-hub, or spoke (informational vertex / navigational centre / intermediate / leaf — see Choosing a role).

A hub or a pillar is a topic’s apex — where authority pools. Everything else is a member of the topic and routes up to that apex. That is the whole ladder: two relationships, not a band per page. The role itself is derived from the apex edge (HUB_OF ⇒ hub, PILLAR_OF ⇒ pillar, otherwise spoke), so it can never go stale against the graph.

How a page gets its role — the engine never guesses. It reads the role from one of three places:

  1. A seed page (gtmesh page add … --role hub) — a source-less page is appointed the apex of its --parent-topic, written as a source: seed node into the graph. This is where your conversion hubs normally come from.
  2. Entity-aware classification (opt-in classification.entity_aware) — a keyword that is only a known entity routes to that entity’s hub, so a brand/entity landing becomes an apex with no signals rule.
  3. reference/signals.csv — a keyword-pattern → role table appoints matching keywords (e.g. " vs " → a sub-hub feeder).

Anything a signals rule doesn’t match falls to the default role: spoke. So a mesh with an empty signals.csv comes out all hubs and spokes: hand-seeded and entity hubs are apexes, and everything else is a spoke — no feeder layer in between. That’s a barbell, not a funnel. To build the middle, add signals rules that appoint your feeder query shapes — comparisons, best-of, buying guides — role: sub-hub (see Tuning §4).

Then there are two different ways a page can enter the mesh, and conflating them is the single most common mistake:

seeds/<class>.csvgtmesh page add (a seed page)
What it isA candidate TERM — a real keyword that has search demand behind itA source-less APEX page — a hub / landing with no keyword data behind it, authored as a source: seed node
Where it comes fromThe harvest skill, ranked against a demand corpusYou (or the operator), decreed by hand via gtmesh page add
Carries metrics?Metrics are joined later from data/raw/ (the file itself never holds Vol/KD)None — there is no keyword to measure
On the calendar?Yes — it is scored and scheduled like any keywordNo — off-calendar; it exists because the topology needs it, not because demand ranked it

This is the bit people get wrong. seeds/<class>.csv is a list of terms with demand — things people search for, that you want to rank for, scored and queued by opportunity. A seed page (gtmesh page add) decrees a page into existence — the converging hubs and commercial landings the whole mesh points at, which have no search query behind them and so are never volume-ranked. One is “what should we write because people are searching for it”; the other is “what must exist because it is the destination.”

Why it matters

The authority layer is what stops the mesh from being a flat pile of pages. Roles and topic membership decide which pages link up to which, and the apex pages you decree with gtmesh page add are the things everything else funnels toward. If you put a destination page into seeds/<class>.csv it would get scored on (non-existent) keyword volume and probably never get built; if you decree a demand keyword as a seed page you would lose its keyword data and its scoring entirely.

How to drive it

  • Tune keyword → role classification in reference/signals.csv.
  • Add apex / pillar pages (the destinations) with gtmesh page add.
  • Fill demand-driven term classes with the harvest skill, which writes seeds/<class>.csv for your review.

Deeper procedure is in Tuning.


3. Slug anchoring, freeze, and rename

What it is

A page’s slug names its HEAD keyword — the volume-ranked primary keyword for that page (set by identity: { anchor: head } in the config), not a divergent source parent_topic. The important property: once a slug is committed, it is frozen. It becomes sticky committed state.

So what changes a URL, and what doesn’t:

EventDoes the URL move?
The head keyword flips on a re-pull (a higher-volume keyword takes the lead)No. The committed slug stays put — no URL churn.
The source parent_topic changesTriggers a redirect action (human-gated).
A rename group-edit in overrides/group-edits.yamlYes — this is the deliberate lever.

Why it matters

URLs are expensive to change — they break links and rankings. If the slug followed the head keyword on every re-pull, your URLs would churn every time the keyword tool reshuffled volumes. Freezing the slug means the catalogue can keep refreshing keyword data underneath a page without ever moving its address by accident.

How to drive it

The only lever that intentionally moves a committed URL is a rename group-edit in overrides/group-edits.yaml. A rename:

  • pins the new slug,
  • keeps the page’s identity (it is the same page),
  • emits a 301 redirect — the old URL becomes a url node with an ALIAS_OF edge to the live page, so old URLs resolve through it forever,
  • and moves the page bundle (the written body and its co-located assets) to the new path.

So: re-pulls never move a URL; only an explicit rename does. The full slug-change / removal detail is in Lifecycle.


4. The SERP layer model

What it is

Some pages carry a funnel value describing the SERP layer they belong to — what the search results for that query actually look like:

  • commercial — the SERP is dominated by landing / product pages (this query is a money page).
  • content — the SERP is informational; this page funnels up into a commercial landing (via funnel_target, a slug or parent_topic).
  • mixed — the SERP is split roughly 50/50; a human call, never auto-resolved.

funnel_target names the landing a content page links up to. Both are frozen at discovery time — the operator classifies them once (by tearing down the real SERP with gtmesh pull serp) and the engine simply applies them. It does not re-derive the layer on every plan.

Why it matters

This is how money pages avoid being buried behind blog posts. A commercial-layer landing ships off-calendar, by business priority — it is exempt from volume scoring (see scoring.exempt with when: { funnel: commercial } in the config). Without this, a high-value landing page with modest search volume would queue behind a pile of higher-volume informational articles and never get built. The SERP layer says: this is a destination, ship it because the business needs it, not because a volume number ranked it.

How to drive it

You normally don’t set funnel by hand — the operator establishes it from the SERP teardown and writes it into seeds/<class>.csv / the seed page it authors (gtmesh page add). The lever you do hold is scoring.exempt in gtmesh.config.yaml, which is what makes the whole commercial layer ship off-calendar. (On a non_commercial mesh nothing carries funnel: commercial, so the rule is inert.) See the walkthrough.


5. Fan-out vs fragmentation

What it is

One parent_topic can legitimately fan out into several typed pages — a glossary entry and a guide for the same term, for example. That is a feature: different page types serve different intents for the same topic.

Fragmentation is the failure mode: a single stray keyword minting a junk extra page that nobody needs.

The guard between them is classification.min_group_members (with a companion, min_group_volume_pct) in gtmesh.config.yaml. A typed sibling group must clear either bar to spawn its own page — at least min_group_members members, or at least min_group_volume_pct of the parent’s volume. If it clears neither, it folds back into the parent’s dominant page as brief material instead of becoming its own page.

# gtmesh.config.yaml → classification # min_group_members: 2 # default 1 = off; set 2 to fold single-member fragments # min_group_volume_pct: 10

Why it matters

Programmatic content lives or dies on this line. Fan-out gives you good coverage (a term explained in a glossary and walked through in a guide); fragmentation gives you thin, near-duplicate pages that dilute the mesh and look like spam. The guard lets you keep the first and stop the second.

How to drive it

The default is min_group_members: 1, which is a no-op (every group spawns). Set it to 2 to fold single-member fragments back into their parent. Tune min_group_volume_pct alongside it if you’d rather gate on share-of-volume. Watch the plan’s output as you change it. More in Tuning.

A related axis is entity fan-out. With classification.entity_aware on (opt-in), every spelling and phrasing of a known entity is pulled under that entity’s canonical hub before the guard runs: brand variants collapse into the one hub, and a real sub-intent («brand + скачать») becomes a sub-page nested under it rather than a sibling of unrelated topics. Because the hub and its sub-pages then share a parent_topic, the same fan-out guard applies — and when the dominant sibling is the hub, a below-threshold sub-intent is dropped (not folded into the hub’s brief), so a stray low-demand intent can’t pollute the hub. See Tuning › Entity-aware classification.

One URL, one page — keyword variants

A keyword tool will happily hand you the same query twice, wearing different punctuation: self priming centrifugal pump and self-priming centrifugal pump, or submersible pump - screwfix and submersible pump screwfix. Slugs strip punctuation, so both forms want the same URL — and only one page can live there.

When the two head keywords are the same query differently punctuated, plan folds them: volumes sum, the highest-volume form becomes the primary_keyword, and the other rides along as a secondary keyword. Nothing is discarded, and a variant-fold warning names what merged.

warn: /glossary/self-priming-centrifugal-pump ← "self priming centrifugal pump" (150) + "self-priming centrifugal pump" (60)

When it can’t prove they’re one query — the heads are genuinely different, or one slug is already pinned by a rename, or the pages came from a split — it says so rather than guessing, and names your two levers in overrides/group-edits.yaml:

  • merges — they are one page. Fold them into a single identity; volumes sum.
  • renames — they’re distinct pages that collided by accident. Pin one to its own URL.

Both apply before identity is resolved, so either one clears the collision on the next plan.


6. Winning a topic — the head is the prize, the tail is the ladder

What it is

A topic is not won by ranking one page for one keyword. It’s won by a cluster: members route authority up to the apex, and the apex becomes able to hold the head term. So “can I win this topic?” is a question about the topic’s whole demand shape, not the difficulty of its head keyword.

Two numbers, answering two different questions:

  • The head carries the value. A topic’s opportunity comes overwhelmingly from its head term — that’s where the volume and the CPC are. The head is the prize.
  • The tail tells you whether you can start. The uncovered keywords that restate the topic’s head/seed terms — mostly questions, mostly little or no CPC of their own — are the ladder. Enough of them at low difficulty means there’s a way in: you cover them, authority pools at the apex, and the head comes into reach later.

So a hard head is a horizon, not a veto. A KD-80 head with 3,000 volume of uncovered KD-10 questions beneath it is a good investment — you win the tail first and climb. The same head with nothing beneath it is the one genuine skip: no entry point, no ladder.

Two ways to get this wrong, and they fail in opposite directions. Judging a topic by its head’s difficulty alone makes you walk away from money you could reach in stages. Judging the tail by its own commercial value makes you walk away from the ladder — those question terms score near zero on volume × cpc because nobody bids on them, which is exactly what you’d expect from a rung rather than a destination.

Why it matters

It’s what makes the build order right, and the build order is most of the game: apex first (authority needs somewhere to pool), then the winnable tail, and only then contend for the head. It’s also why gtmesh next is apex-first and paced — see The refresh loop.

How to drive it

  • gtmesh topic candidates is the selection gate — it ranks topics and gives each a verdict (build / promote / invest / hold).
  • gtmesh topic fill <value> names a topic’s uncovered demand — the rungs you haven’t built yet, content and commercial.
  • gtmesh next turns the choice into a paced build batch.

How the engine measures it

gtmesh topic candidates computes both sides and takes the better of them:

QuestionHow it’s measured
headCan we take the head term directly?The head keyword’s own difficulty, read from the demand bag
tailIs there a way in?The median difficulty of the topic’s uncovered demand

winnability = max(head, tail) — which is what makes a hard head a horizon rather than a veto. The output names which side carried it (via head / via tail) and, when there’s a ladder, prints the easiest rungs: way in: "how to prime a submersible pump" (KD 5, 300 vol) · ….

Three deliberate choices behind that:

  • Median, not minimum. A single stray KD-0 term shouldn’t declare a whole topic winnable. The median asks “what does a typical rung cost?”
  • Uncovered only. A keyword some page already targets isn’t a rung left to climb. (Whether that page is built yet is gtmesh next’s question, not this one.)
  • Unscored ≠ easy. A keyword the provider never rated carries no difficulty at all, so it counts as uncovered demand but never as evidence of an easy way in.

When you’ll see head KD — or tail KD —. Difficulty is genuinely optional data: providers return no score for much of the low-volume long tail, and gtmesh records that as unknown rather than 0. A topic with no difficulty evidence on either side scores win 1.00 and reports (no KD data) — the ranking is then effectively opportunity-only, which is the honest answer when nothing is known. A tail KD — with 0 vol uncovered means something different and good: every keyword under that topic already has a page.


7. The invariants

What it is

A short list of properties GoToMesh guarantees and will not break:

  • The CLI is deterministic and LLM-free. Same inputs → byte-identical graph. The only step that uses a model is the article-writer skill, which writes page bodies.
  • The committed graph IS the state file. graph/nodes.jsonl + graph/edges.tsv are master state — never hand-edited. You author your domain into it through validated verbs (gtmesh graph upsert for entities, gtmesh page add for source-less pages); apply, seal, and the lifecycle commands are the derived mesh’s only writers.
  • plan is read-only. It computes the diff and prints it; it writes only a derived, git-ignored planfile. It never changes state.
  • Discovery is import, not plan. The open-ended, networked research (the operator and harvest skills) runs outside the deterministic loop and emits committed, human-reviewed files. The engine then consumes those files deterministically.

Why it matters

These are the reasons you can trust the mesh: re-running apply is always safe, the diff is reviewable because state is a committed file, and the one non-deterministic, model-driven step (writing prose) is firewalled off from the structural engine. The boundary between “the deterministic engine” and “the model-driven writer / researcher” is the whole design.

How to drive it

You don’t drive the invariants — you rely on them. Concretely: never hand-edit the committed graph (graph/nodes.jsonl, graph/edges.tsv); use plan freely (it costs nothing and changes nothing); and treat the files the operator / harvest write as input you review and commit, not as engine state. The lifecycle that flows from these rules is in Lifecycle.

Last updated on