Skip to content

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

.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.ts

Key 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)

Nodes can be nested. Children inherit parent aspects automatically.

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: uses

Available types: calls, uses, extends, implements, emits, listens. The type is descriptive — it documents the nature of the dependency.

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.

How aspects reach nodes

Aspects propagate through seven channels:

ChannelHow it works
OwnListed in aspects: field of yg-node.yaml
AncestorParent node aspects inherited by all children
Own typeDefault aspects defined per node type in architecture file
Ancestor typeArchitecture defaults from parent node types
FlowsAll flow participants inherit flow-level aspects
PortsConsumer must satisfy port-required aspects
ImpliedAspects 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-tracking

Flow-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

  1. Agent reads context before writing code (yg context --file <path>) and sees which aspects apply
  2. Agent writes code
  3. Agent runs yg approve — reviewer (LLM) checks each aspect's content.md against the source code
  4. Reviewer says pass → approved, new baseline hash recorded
  5. 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 (with LLM review) locally, CI just verifies it happened.


Architecture file

yg init creates .yggdrasil/yg-architecture.yaml with default node types. This file controls what types of nodes are allowed and can set default aspects per type. The defaults work out of the box — customize when you need to. 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"
  service:
    description: "Component providing functionality to other nodes"
    aspects: [requires-audit]
    relations:
      calls: [service, library]
      uses: [library]
  library:
    description: "Shared utility code with no domain knowledge"
  infrastructure:
    description: "Guards, middleware, interceptors"
  data:
    description: "Database layer, persistence, and data access"

Setting aspects: [requires-audit] on the service type means every service node automatically inherits that aspect without listing it explicitly. The relations constraint limits which node types a service can depend on.

This is one of the seven aspect distribution channels ("by node type") and also how you enforce structural rules across your architecture.


The reviewer

The reviewer is an LLM that reads source code and checks it against aspect rules during yg approve. It's a separate call from your coding agent — one LLM verifying the work of another.

yg approve sends each aspect's content.md plus the relevant source files to the reviewer. The reviewer responds with SATISFIED or NOT SATISFIED per aspect. One LLM call per aspect per node.

Cost: A typical approve for a node with 3 aspects and 5 source files makes 3 LLM calls. Using a fast model (Haiku, GPT-4o-mini, Gemini Flash) keeps cost under a few cents per approval. For local review, Ollama runs on your machine with no API cost.

Consensus: Set consensus: 3 (or any odd number) to run multiple review passes and take the majority vote. Higher confidence, proportionally higher cost.

False positives: If the reviewer rejects compliant code, the fix is improving the aspect's content.md — make the rule clearer and more specific. The escape hatch is better rules, not bypassing enforcement.

Inline Suppress

Source code comments can carry a yg-suppress marker to waive a specific aspect for the surrounding code. The reviewer honors these markers and treats the suppressed code as satisfied.

Format: yg-suppress(<aspect-path>) <reason>

  • <aspect-path> — full aspect path (e.g., cqrs/single-responsibility)
  • <reason> — required free-text explanation
typescript
// yg-suppress(cqrs/single-responsibility) brownfield handler, refactor planned

Place the marker near the code that violates the aspect. The reviewer interprets scope contextually — a marker in a function applies to that function, at file level it applies to the entire file.

Agent behavior: Agents may propose adding a suppress marker but must never write one without explicit user confirmation.