Skip to content

Config file: .yggdrasil/yg-config.yaml

yg init creates this file. A bare, non-interactive yg init (no --provider) writes it with no reviewer: section at all — a keyless, script-only start. A reviewer is configured separately, interactively or via --provider [--model] [--endpoint], whenever the graph gains its first judgment (LLM) rule. yg init --upgrade lifts the graph's config version to the current one and refreshes the agent-rules files.


Schema

Required

  • version — Schema version managed by the CLI. Do not edit manually. Run yg init --upgrade to upgrade.

Conditionally required

  • reviewer — Reviewer configuration; when present, must contain tiers with at least one entry. Required only once a judgment (LLM) rule is actually effective in the graph — a script-only / keyless project (deterministic aspects only, or none) needs no reviewer: section, and yg check does not ask for one until an LLM aspect exists. Configured via yg init (interactively, or non-interactively with --provider); see Reviewer tiers below.
  • reviewer.default — Tier name aspects fall back to when they don't declare one. Required when reviewer.tiers has more than one entry; optional with exactly one tier.

Optional

  • coverage — Controls which files must be mapped to a node (see Coverage config below).
  • quality — Quality thresholds (see Quality config below).
  • parallel — How many LLM (reviewer) verifications run concurrently (positive integer, default 1). Governs only the reviewer fill phase, where the cost is network latency. Deterministic checks ignore it — they are CPU-bound and run across a worker-thread pool sized automatically from your machine's cores (no configuration; never affects verdicts, only speed).
  • debug — Set true to append all CLI output to .yggdrasil/.debug.log.
  • auto_approve — Auto-fill mode for bare yg check (default false; see Auto-approve config below).
  • signals — Attention-layer switches (optional). Its only key today is attention (default true): the advisory "structurally unusual" note in yg context --file. Set false to silence it. See Signals below and Structural attention.
  • events — Committed-events opt-in (optional). Its only key today is committed_llm (default false): opt into a committed, team-shared record of LLM verification events. See Events below.

Those nine are the whole of it — version, reviewer, coverage, quality, parallel, debug, auto_approve, signals, events.

A typo at the top level is silent

The parser reads the nine keys above and ignores anything else it finds at the top level, with no error and no warning. So auto_aprove: full does not enable auto-approval — it does nothing at all, and the check that would tell you so does not exist. Two nested places are guarded: a misspelled key directly under reviewer: or inside a tier is a hard config-reviewer-unknown-key / config-tier-unknown-key error, and signals: and events: reject unknown keys too. Copy the names from this page rather than typing them from memory, and confirm a setting took effect by watching the behaviour change.

Node types are defined in the separate architecture file (.yggdrasil/yg-architecture.yaml), not in yg-config.yaml.


Full annotated example

yaml
version: "5.1.0"

reviewer:
  default: standard                 # Required when more than one tier; optional with one
  tiers:
    standard:                       # Tier name — referenced by aspect reviewer.tier
      provider: ollama              # LLM provider
      consensus: 1                  # Votes per aspect (odd integer >= 1)
      max_prompt_chars: 50000       # Cap on the assembled prompt (optional; absent defaults to 50000)
      config:
        model: qwen3
        endpoint: http://localhost:11434
        temperature: 0

coverage:                             # Optional — controls which files must be mapped
  required:                           # Unmapped files under these roots are a blocking error
    - "/"                             # Default: whole repo
  excluded: []                        # Files under these roots are silently ignored

quality:
  max_direct_relations: 10

parallel: 10                          # Concurrent LLM verifications (reviewer phase only)
debug: false
auto_approve: false   # false (default) | deterministic | full

signals:                              # Optional — attention-layer switches
  attention: true                     # The "structurally unusual" note in yg context --file (default true)

events:                               # Optional — committed-events opt-in (default off)
  committed_llm: true                 # Commit + share LLM verification events (default false)

Reviewer tiers

Reviewer configuration uses named tiers. Each tier is an independent LLM configuration. Aspects target a tier via reviewer.tier: <name> in yg-aspect.yaml. If no tier: is declared on an aspect, the aspect uses reviewer.default from the config.

reviewer.default

The tier name aspects fall back to when they don't declare reviewer.tier:.

  • Required when reviewer.tiers has more than one entry — the validator emits config-default-tier-missing otherwise.
  • Optional when reviewer.tiers has exactly one entry; the single tier is the implicit default.
  • Must reference a key under reviewer.tiers.

reviewer.tiers.<name>

Tier name regex: ^[a-zA-Z][a-zA-Z0-9_-]{0,62}$. The literal name default is reserved (it would clash with reviewer.default visually). Convention: standard for the primary tier. Add a second tier (e.g. deep) for aspects that need a higher-capability model.

yaml
reviewer:
  default: deep
  tiers:
    standard:
      provider: anthropic
      consensus: 3
      config:
        model: claude-opus-4-7
        temperature: 0
    deep:
      provider: ollama
      consensus: 1
      config:
        model: qwen3
        endpoint: http://localhost:11434

An aspect targeting the standard tier (overriding the default):

yaml
reviewer:
  type: llm
  tier: standard

An aspect with no explicit tier uses reviewer.default (deep in the above example):

yaml
reviewer:
  type: llm

Fields per tier

FieldRequiredDescription
provideryesOne of the supported providers (see below)
consensusyesPositive odd integer. 1 = single call. 3 = majority vote.
max_prompt_charsnoPositive integer. Caps the assembled-prompt length for LLM pairs on this tier (see Prompt-size gate). Absent defaults to 50000. yg init writes 50000.
config.modelrequired for ollama / openai / anthropic / google / openai-compatible; optional for the CLI providersProvider-specific model identifier. Omitted on a CLI provider it defaults to: claude-codehaiku, codexo4-mini, gemini-cligemini-2.5-flash.
config.temperaturenoSampling temperature. Defaults to 0.
config.endpointrequired for openai-compatible (ollama defaults to http://localhost:11434)API endpoint URL
config.timeoutnoPer-call timeout in seconds. Defaults to 300. Honored by CLI providers and the ollama provider; other hosted API providers ignore it.
config.api_keynoProvider API key. Takes precedence over the provider's environment variable. Do not put it in yg-config.yaml — supply it through the gitignored yg-secrets.yaml overlay (see the Secrets section below).

Unknown config.* keys are silently ignored (no error, no warning) — only the keys listed above are read.

Supported providers

ProviderTypeNotes
ollamalocalNo API cost; requires local install
anthropicAPIRequires ANTHROPIC_API_KEY or yg-secrets.yaml
openaiAPIRequires OPENAI_API_KEY
googleAPIRequires GOOGLE_API_KEY
openai-compatibleAPIAny OpenAI-compatible endpoint
claude-codeCLIDelegates to the installed claude CLI
codexCLIDelegates to the installed codex CLI
gemini-cliCLIDelegates to the installed gemini CLI

CLI providers (claude-code, codex, gemini-cli) require no API key — they delegate to the installed CLI tool.


Secrets and local overrides

.yggdrasil/yg-secrets.yaml is a deep-merge overlay over yg-config.yaml (gitignored by default). It mirrors the same shape, and any field in it wins — use it for a tier's API key, or to point a named tier at a different provider/model/endpoint on your machine:

yaml
# .yggdrasil/yg-secrets.yaml — gitignored, never commit
reviewer:
  tiers:
    standard:
      config:
        api_key: sk-ant-...

Because only the tier name is folded into a verdict's hash, a local override never invalidates recorded baselines: the committed config names a canonical reviewer, and each machine points the same named tier at its own provider, model, or key.

API providers also check environment variables: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY. If the env var is set, the key is not needed in yg-secrets.yaml.

yg-config.yaml itself must never contain credentials. Commit it to the repository.


Coverage config

yaml
coverage:
  required:
    - src/                  # files under src/ must be mapped — unmapped is a blocking error
  excluded:
    - vendor/               # files under vendor/ are silently ignored
    - "**/*.generated.ts"   # glob: generated files anywhere are ignored

Controls which git-tracked files must be mapped to a node in yg check.

  • required — List of roots. Files under a required root that are not mapped to any node produce an unmapped-files error (blocks CI). Default: ["/"] (the whole repo — reproduces the previous always-map-everything behavior). An explicit empty list [] means require nothing — every uncovered file (outside excluded/nested) becomes a non-blocking uncovered-advisory warning and nothing blocks (pure-advisory adoption: you still see the full uncovered surface, but CI stays green on coverage). The empty list only takes effect when written explicitly; omitting the whole coverage block keeps the ["/"] default. yg init writes an explicit required: [] into a fresh yg-config.yaml, so newly-initialized projects start in require-nothing mode (green from the first check) — add roots as you bring areas under enforcement.

  • excluded — List of roots. Files under an excluded root are silently ignored regardless of other rules.

  • Roots accept the same forms as a node mapping: entry — an exact file, a directory prefix (e.g. src/), or a minimatch glob (* within a path segment, ** across segments). So excluded: ["**/*.generated.ts"] ignores generated files anywhere, and required: ["services/*/api/**"] scopes the blocking tier to a pattern. / still means the whole repo.

  • The files Yggdrasil maintains at your repo root count like any other. yg init writes and keeps up to date AGENTS.md, CLAUDE.md, .clinerules/yggdrasil.md and a .gitattributes entry. Under a whole-repo required (including the absent-block default) they are unmapped files like any other, so they become blocking errors the moment yg init --upgrade adds them to an existing project. They are repository plumbing rather than project source — the usual answer is to exclude them (yg init --upgrade prints this stanza whenever it applies to your project, and never edits the file itself), though mapping them to a node works equally well:

    yaml
    coverage:
      excluded:
        - AGENTS.md
        - CLAUDE.md
        - .clinerules/
        - .gitattributes
  • Files that match neither a required nor an excluded root produce a non-blocking uncovered-advisory warning.

  • Subtrees that contain their own nested .yggdrasil/ are auto-skipped by all repo-walking checks — they are governed by their own graph, not the root graph.

Each file is scored against all roots independently; the longest matching root (or pattern, by length) wins, and on an equal-length tie between a required and an excluded root, excluded wins.


Prompt-size gate

A tier's optional max_prompt_chars caps the length of the prompt the LLM reviewer assembles for each pair. The prompt for each LLM pair is composed of: the rule text (content.md), any static reference files, the unit's subject files, and — when the aspect ships a companion.mjs — any companion files the hook resolved for that unit. All of these count toward the limit.

yg check measures the assembled prompt for every expected LLM pair and reports prompt-too-large — a blocking error — when it exceeds the resolved tier's limit. The check is deterministic and costs nothing; deterministic pairs have no prompt and are never subject to it.

yaml
reviewer:
  tiers:
    standard:
      provider: anthropic
      consensus: 1
      max_prompt_chars: 50000
      config: { model: claude-haiku-4-5, temperature: 0 }

When a pair trips the gate, the remedies in safety order are:

  1. Narrow scope.files on the aspect so non-target payload (fixtures, generated files) drops out of the subject set.
  2. Switch the aspect to per: file — only if the rule is file-local; a per-file reviewer cannot judge a cross-file rule.
  3. Split the node into children.
  4. Raise the limit or move the aspect to a higher-limit tier — but tier choice is part of a pair's identity, so a tier edit re-verifies every pair resolving to it.

max_prompt_chars is a gate, not a verdict input: lowering it can make an already-verified pair trip the gate without invalidating its recorded verdict.


Quality config

yaml
quality:
  max_direct_relations: 10        # Max out-edges per node (high-fan-out warning)

max_direct_relations fires a warning when a node's outgoing relation count exceeds it — a signal that the node may be doing too much. It is the only quality threshold.

Per-node reviewed-seam override

A node may declare its own justified ceiling in its yg-node.yaml, which sets that node's own limit, replacing the global default for it:

yaml
max_direct_relations:
  limit: 21
  reason: "Single auditable gateway that concentrates this coupling by design."

The declared limit sets the ceiling for that node only — it may be higher than the global (a genuine single-responsibility seam — one auditable gateway or orchestrator that concentrates coupling by design, where splitting would defeat the architecture) or lower (holding this node to a stricter budget than the rest of the repo). Only the safe direction adds warnings: a limit below the global makes the node stricter. The global default still governs every other node, and this node still warns if it exceeds the number it declares here — so the allowance sanctions a specific, reviewed count rather than loosening the check globally. Both fields are required; a partial or malformed override — including a limit below 1 — is ignored and the strict global default applies. It is a check parameter only — never a verification input — so declaring it re-verifies nothing. The number and its justification are surfaced by yg context --node and yg schemas read node, keeping the exception explicit and auditable in the graph.


Local state — what never gets committed

Everything Yggdrasil derives locally lives under .yggdrasil/ and is kept out of git by a single .yggdrasil/.gitignore that yg init writes and every yg init --upgrade tops up (missing lines are appended; lines already there, and any of your own, are left alone). There is no repo-root entry to maintain separately.

EntryWhat it is
yg-secrets.yamlYour local overlay — provider keys and machine-specific tier overrides.
.symbols-cache/A retired predecessor of the cache below. Nothing writes it any more; the entry stays so leftovers in an older checkout keep being ignored.
.ast-cache/The relation pass's content-addressed per-file parse cache — the live one.
.debug.logThe opt-in command log written when debug: true.
.yg-lock.deterministic.jsonThe script-rule verdict cache — rebuilt free and keyless by yg check --approve --only-deterministic.
.yg-events.jsonlThe verdict-events telemetry sidecar (see Verdict-events sidecar).
.yg-fill-divergence.logForensic evidence, written only when a single run disagrees with itself because something outside Yggdrasil rewrote a tracked file mid-run (see Running in parallel).
.feature-field.jsonThe silent structural-deviation index behind the structural-attention hint.

Every one of them is rebuildable, so a fresh clone missing all of them is a normal state, not a broken one. The only thing a fresh clone notices is the absent verdict cache: those pairs read as unverified until yg check --approve --only-deterministic rematerializes them.

The committed side is the graph itself — yg-config.yaml, yg-architecture.yaml, the model/, aspects/ and flows/ trees, the two committed lock files, the incident ledger, the attention-decision record, and (when opted in) the shared events stream.


Upgrading

bash
yg init --upgrade

Lifts the graph's config version to the current one and refreshes the agent-rules files (the AGENTS.md digest block, the CLAUDE.md import, and .clinerules/yggdrasil.md) to the installed CLI's current content — no flag needed to say which agent to write for, since the same files are written for every agent. It also sweeps away any file a retired per-platform installer left behind from an older CLI. (Upgrading a pre-5.1.0 graph also removes the now-retired on-disk schemas/ directory; schemas are read with yg schemas read <name> instead.)

The legacy single-section reviewer format (flat provider keys + reviewer.active) is not migrated — the upgrade leaves the reviewer: block untouched, so a config still in that shape then fails yg check with a config-reviewer-unknown-key error on active. Convert it to reviewer.tiers by hand (see the tier fields above). Retired fields are SILENTLY IGNORED — a yg-config.yaml still carrying quality.max_node_chars, per-tier config.references: size caps, or other retired config.* keys (e.g. config.context_length_field) produces no error and no warning; the parser simply does not read them. Review the diff after upgrade and delete the dead lines by hand. This is distinct from the parser's unknown-KEY guard: a typo'd key under reviewer: or under a tier still fails yg check with a clear config-reviewer-unknown-key / config-tier-unknown-key error — a key typo is caught, a retired-field cleanup is not. Run from the repository root only. Review the diff before committing.


Auto-approve config

auto_approve controls what bare yg check does when you run it without explicit --approve, --no-approve, or --only-deterministic flags.

ValueBehavior
false (default)Read-only: recomputes hashes, validates, reports. Writes nothing, makes no LLM calls, needs no keys.
deterministicBehaves like yg check --approve --only-deterministic — fills only deterministic pairs (free, keyless), writes only the gitignored cache.
fullBehaves like yg check --approve — fills every unverified pair including LLM pairs.

Precedence: explicit CLI flags always override auto_approve. Passing --approve, --no-approve, or --only-deterministic on the command line takes effect regardless of what the config says.

CI note: CI and pre-commit scripts should always use explicit flags (yg check --approve --only-deterministic) — the CI-is-free-and-keyless guarantee is about explicit flag use, not auto_approve. Set auto_approve for local developer convenience only.

When a fill triggered by auto_approve produces a PASS, the result line shows (auto-filled) to indicate that verdicts were written during this run.

yaml
# .yggdrasil/yg-config.yaml
auto_approve: deterministic   # fill the deterministic cache automatically on bare yg check

Signals

signals is an optional section that turns attention-layer hints on or off. It is absent by default, which leaves every signal at its default.

KeyTypeDefaultEffect
attentionbooleantrueThe advisory "structurally unusual" note in yg context --file. Set false to silence it.
yaml
# .yggdrasil/yg-config.yaml
signals:
  attention: false   # silence the "structurally unusual" note in yg context --file

The note is purely advisory: it never blocks a check and never changes any verification result whether it is on or off. attention must be a boolean, and signals accepts no other key — a misspelled key is rejected so a typo can't silently leave the note enabled. See Structural attention for what the note means and its honest limits.


Events

events is an optional section that controls where LLM verification events are recorded. It is absent by default, which keeps every event in a local, gitignored file (see Verdict-events sidecar).

KeyTypeDefaultEffect
committed_llmbooleanfalseOpt into a committed, team-shared record of LLM verification events.
yaml
# .yggdrasil/yg-config.yaml
events:
  committed_llm: true   # commit + share LLM verification events (default off)

When committed_llm is on, each LLM verification event is appended to a committed file, .yggdrasil/yg-events.llm.jsonl, instead of the local sidecar — a single home per event, so nothing is double-counted. The committed file is:

  • LLM-only. Deterministic checks, drill runs, and diagnostic runs stay local, so a free, keyless CI run (yg check --approve --only-deterministic) adds nothing to it — zero churn.
  • Union-merged. yg init marks it merge=union in .gitattributes, so events appended on different branches combine on merge instead of conflicting.
  • Rationale-stripped. The refusal reason is omitted from the shared copy (it can carry code fragments); the local copy keeps it.

Turning the opt-in on or off never changes any verification result or its hash — it invalidates nothing. committed_llm must be a boolean, and events accepts no other key — a misspelled key is rejected so a typo can't silently leave the shared record disabled. A machine on an older CLI writes only locally and does not contribute to the shared file, so a reader that combines the two says as much rather than treating the committed record as complete.


Notes

  • yg-node.yaml is a reserved filename in model directories.
  • Node types are defined in yg-architecture.yaml, not yg-config.yaml.