Appearance
Yggdrasil stores verification rules in a graph under .yggdrasil/ in your repository. The graph has three main elements: nodes, aspects, and flows.
The agent creates and maintains these. You tell the agent what matters, and it builds the graph structure.
Directory structure
text
.yggdrasil/
yg-config.yaml ← project config (reviewer, quality thresholds)
yg-architecture.yaml ← node type definitions
model/ ← nodes (your system's components)
orders/
order-service/
yg-node.yaml
payments/
payment-service/
yg-node.yaml
aspects/ ← cross-cutting rules
requires-audit/
yg-aspect.yaml
content.md
rate-limiting/
yg-aspect.yaml
content.md
flows/ ← business processes
checkout/
yg-flow.yaml
schemas/ ← YAML schemas (auto-generated by yg init)Nodes
A node represents a component in your system: a module, service, library, or any cohesive piece of code. Each node is a directory under .yggdrasil/model/ with a yg-node.yaml file.
yaml
# .yggdrasil/model/orders/order-service/yg-node.yaml
name: OrderService
type: service
description: "Manages order lifecycle: creation, validation, state transitions"
aspects:
- requires-audit
- rate-limiting
relations:
- target: payments/payment-service
type: calls
mapping:
- src/orders/
- src/orders.tsKey fields:
- type — must match a type defined in
yg-architecture.yaml(e.g.module,service,library) - aspects — list of aspect IDs this node must satisfy
- relations — outgoing dependencies to other nodes (see Relations below)
- mapping — source files and directories this node owns (paths relative to repo root). Entries accept minimatch glob patterns:
*matches within a single path segment,**matches across segments. For example,src/db/*Repository.csmaps only files matching that pattern insrc/db/;src/**/*.tsmaps all TypeScript files anywhere undersrc/. Plain entries (no glob metacharacters) behave as before: an exact file or a directory prefix that covers all files beneath it.
Nodes can be nested. Children inherit parent aspects automatically.
Node size budget
The LLM reviewer sends all of a node's mapped source (plus any aspect reference files) in one prompt, so a node has a character budget: quality.max_node_chars in yg-config.yaml, default 40000. A node that exceeds it produces an oversized-node error in yg check — split it into children or trim its mapping. The budget applies only to nodes an LLM reviewer actually reads — those with at least one non-draft LLM aspect. Nodes reviewed only by deterministic aspects (check.mjs reads files programmatically, with no context window) and aspect-less nodes are never bounded. Binary files (images, fonts, archives, etc.) count 0 toward the budget automatically. For a large unsplittable text artifact — a generated lockfile, for example — opt the node out with sizeExempt: { reason: "..." }.
Relations
Relations declare dependencies between nodes. They serve two purposes: yg impact uses them to calculate blast radius, and yg check validates that targets exist and port contracts are satisfied.
yaml
relations:
- target: payments/payment-service
type: calls
- target: shared/logger
type: usesAvailable types: calls, uses, extends, implements, emits, listens. Relation types are constrained by the architecture — each node type declares which target types it may reach per relation type, and yg check rejects relations that violate those constraints. The event types emits and listens must be paired.
Ports
A node can declare ports — named entry points with required aspects. When another node consumes a port, it must satisfy the port's aspects.
yaml
# payments/payment-service/yg-node.yaml
ports:
charge:
description: "Charge a payment method"
aspects: [correlation-tracking]yaml
# orders/order-service/yg-node.yaml
relations:
- target: payments/payment-service
type: calls
consumes: [charge]Now orders/order-service must satisfy the correlation-tracking aspect because it consumes the charge port. This is the "port contracts" channel in the aspect propagation table below.
Minimal nodes (coverage without enforcement)
For parts of the codebase you don't need to enforce rules on, create a node without aspects:
yaml
name: LegacyAuth
type: module
description: "Legacy authentication system — do not modify"
mapping:
- src/legacy/auth/Nodes without aspects auto-approve instantly — no hashing, no LLM review. They satisfy the coverage requirement without adding enforcement overhead. When you're ready to enforce rules, add aspects to the node.
Aspects
An aspect is a cross-cutting architectural rule. It's a directory under .yggdrasil/aspects/ with a yg-aspect.yaml and one or more .md content files.
yaml
# .yggdrasil/aspects/requires-audit/yg-aspect.yaml
name: Audit Logging
description: "Every mutation must emit an audit event"markdown
<!-- .yggdrasil/aspects/requires-audit/content.md -->
Every public mutation endpoint must emit an audit event before returning.
Use the shared `auditLog.emit()` utility. Do not build custom audit logic.
The event must include: user ID, action, timestamp, affected resource ID.The content .md files are the actual rules. The reviewer reads them and checks whether the source code satisfies them. Write them as clearly as you would write a code review comment.
Aspects are verified by reviewers — Yggdrasil ships two reviewer types (LLM and deterministic). See Reviewers for the decision matrix, authoring guides for content.md, check.mjs (deterministic), suppression mechanics, and edge cases.
The implies field
An aspect can declare that it implies other aspects. Implied aspects are included recursively — if A implies B and B implies C, then a node with A automatically gets B and C as well.
yaml
# .yggdrasil/aspects/requires-audit/yg-aspect.yaml
name: Audit Logging
description: "Every mutation must emit an audit event"
implies:
- requires-loggingImplied aspects must exist in the graph. Cycles are forbidden — yg check detects them. Use implies when one rule logically requires another to be satisfied first. For example, audit logging only makes sense if diagnostic logging is also active.
Reference files
LLM aspects may declare supporting files via references: in yg-aspect.yaml. The reviewer reads them as authoritative context; the agent sees them under read: in yg context.
yaml
references:
- docs/error-codes.md
- path: source/lib/codes.ts
description: "Source of truth for error code constants."Changes to referenced files cascade like changes to content.md — every node where the aspect is effective is re-approved. Run yg impact --file <ref> before editing a widely-referenced file. Size limits are configured per reviewer tier in yg-config.yaml.
Aspect status
Each aspect carries a status — draft, advisory, or enforced — that controls whether the reviewer runs and how violations surface:
| Status | Reviewer runs? | Refused renders as |
|---|---|---|
draft | no | n/a (skipped) |
advisory | yes | warning |
enforced | yes | error (blocks CI) |
Status defaults to enforced. Set status: advisory on a new rule to gather signal across the repo without blocking CI; promote to enforced once you trust it. Status can be set on the aspect itself or overridden on any attach site (node, type, flow, port). The effective status on a node is the strictest declared across all channels — bumping up is fine, silent downgrades are rejected.
See Aspect Status for the full lifecycle, status_inherit on implies edges, drift semantics, and migration from 4.x.
How aspects reach nodes
Aspects propagate through seven channels:
| Channel | How it works |
|---|---|
| Own | Listed in aspects: field of yg-node.yaml |
| Ancestor | Parent node aspects inherited by all children |
| Own type | Default aspects defined per node type in architecture file |
| Ancestor type | Architecture defaults from parent node types |
| Flows | All flow participants inherit flow-level aspects |
| Ports | Consumer must satisfy port-required aspects |
| Implied | Aspects included via implies chains (recursive) |
One aspect can reach dozens of nodes through these channels. When you change an aspect's content, every node that uses it gets flagged for re-approval.
Flows
A flow describes a business process that spans multiple nodes. It's a directory under .yggdrasil/flows/ with a yg-flow.yaml.
yaml
# .yggdrasil/flows/checkout/yg-flow.yaml
name: Checkout
description: "Customer places order, payment is processed, inventory reserved"
nodes:
- orders/order-service
- payments/payment-service
- inventory/inventory-service
aspects:
- correlation-trackingFlow-level aspects (like correlation-tracking above) propagate to all participants. This is useful for cross-cutting requirements that apply to an entire business process.
The enforcement loop
- Agent reads context before writing code (
yg context --file <path>) and sees which aspects apply - Agent writes code
- Agent runs
yg approve— each aspect's reviewer checks the source code against its rules. LLM aspects call the model withcontent.md; deterministic aspects runcheck.mjslocally with no LLM call - Reviewer says pass → approved, new baseline hash recorded
- Reviewer says fail →
aspect-violation, agent must fix and re-approve
yg check is the CI gate. It compares file hashes — no LLM calls, runs instantly. If a file changed since its last approve, check fails. The agent does the approve (running the relevant reviewer) locally, CI just verifies it happened.
Architecture file
yg init creates .yggdrasil/yg-architecture.yaml with an empty node_types: {} and commented examples — types are defined per project. This file controls what types of nodes are allowed and can set default aspects per type. You define the types your project needs; the agent knows the schema and can modify this file on your behalf.
yaml
node_types:
module:
description: "Business logic unit with clear domain responsibility"
log_required: false
service:
description: "Component providing functionality to other nodes"
aspects: [requires-audit]
log_required: true
enforce: strict
parents: [module]
relations:
calls: [service, library]
uses: [library]
when:
path: "src/**/*.service.ts"
library:
description: "Shared utility code with no domain knowledge"
when:
path: "src/shared/**"Fields per type:
description— Human-readable label shown inyg treeandyg type-suggestoutput.aspects— Default aspects automatically applied to every node of this type. This is one of the seven aspect distribution channels ("by node type"). Use it for cross-cutting rules that all nodes of a type must satisfy — e.g. everyservicemust have audit logging. Runyg impact --type <id>before adding an aspect here to see how many nodes will be affected.log_required— Whetheryg approverequires at least one log entry before running the reviewer. Defaults totrue. Set tofalsefor types where business reasoning entries aren't needed (e.g. test suites, config nodes).enforce: strict— When set to the string literalstrict, every source file matched by the type'swhenpredicate must belong to exactly one node of this type. Files matched bywhenbut not in any node'smapping:produce atype-strict-orphanerror inyg check; files mapped to a node of the wrong type produce atype-strict-misplacederror. Use this to prevent new files from being added to the codebase without graph coverage.parents— List of type IDs that nodes of this type may nest under. If specified,yg checkrejects nodes placed under an unlisted parent type. Omit to allow any parent.relations— Map of relation type → allowed target types. Restricts which node types a node of this type may declare relations to.yg checkrejects relations that violate these constraints.when— Predicate that classifies files into this type. Can match onpath(glob pattern) and/orcontent(text patterns). Used byyg type-suggestand byenforce: strictbackward coverage. See Conditional Aspects for the full predicate grammar — the same grammar applies here. A type with nowhenis organizational: it can act as a hierarchy parent but its nodes cannot have a non-emptymapping:.
Next
- Reviewers — LLM and deterministic reviewer types, authoring guides, suppression, drift, edge cases
- Conditional Aspects —
whenpredicates for selective aspect application - CLI Reference — full command surface