# The Systems file format

A system in [Systems](https://systems.sbc.me) is one short YAML file. It names the parts of a situation, how they push on each other, the feedback loops those pushes make, and the places worth pushing. Add a few equations and it also runs over time.

The reader is forgiving. Anything it can't use shows up as a problem in the Source lens instead of breaking the page, and most keys accept a synonym or two, so a file written from memory usually works. Only `elements` is needed; everything else is optional.

- Short version, for agents: <https://systems.sbc.me/llms.txt>
- Every example as plain text: `https://systems.sbc.me/systems/<id>.yaml`, like <https://systems.sbc.me/systems/bathtub.yaml>

## Two small files

A sketch, with no numbers. Most systems about a life or a relationship look like this.

```yaml
id: inbox
title: The inbox
compression: Answering fast teaches people to write, so the faster the inbox gets cleared the faster it fills.
question: Why does clearing the inbox never make it smaller?
behavior: cleared every evening, full again by morning
scale: personal

elements:
  inbox: { kind: stock, label: Unanswered email }
  arriving: { kind: flow, label: Email arriving, to: inbox }
  replying: { kind: flow, label: Replying, from: inbox }
  stress: Stress
  speed: How fast I answer
  expectations: What people expect of me
  right_away:
    kind: belief
    label: I have to answer right away
    tier: 2

links:
  - inbox -> stress +
  - stress -> speed +
  - speed -> replying +
  - speed -> expectations + slow | people learn how fast you answer
  - expectations -> arriving +
  - right_away -> speed +

loops:
  B1:
    label: Clearing it
    path: [inbox, stress, speed, replying]
  R1:
    label: Fast answers bring more email
    path: [speed, expectations, arriving, inbox, stress]

levers:
  twice_a_day:
    label: Answer twice a day, at set times
    rung: 5
    at: [speed]
    note: A rule people can learn, so expectations settle lower.
    effort: 2
    reach: 4

questions:
  - Which emails actually need an answer the same day?
```

The same idea with numbers, so it runs. This is the first system in *Thinking in Systems*.

```yaml
id: bathtub
title: The bathtub
compression: A stock only changes through its flows, so its level is the memory of everything that went in and out.
question: If you turn the tap down, why does the tub keep filling for a while?
behavior: goal-seeking
scale: thing

elements:
  water: { kind: stock, label: Water in the tub, unit: liters, start: 10 }
  tap: { kind: flow, label: Tap, to: water, unit: liters/min, rate: tap_opening }
  drain: { kind: flow, label: Drain, from: water, unit: liters/min, rate: water * plug / 10 }

params:
  tap_opening: { label: Tap opening, value: 6, range: [0, 15], unit: liters/min }
  plug: { label: Plug hole size, value: 1, range: [0.2, 3], step: 0.1 }

loops:
  B1:
    label: The fuller it gets, the faster it drains
    path: [water, drain]

levers:
  bigger_plug:
    label: Make the plug hole bigger
    rung: 8
    set: { plug: 2 }
    note: Strengthens the balancing loop, so the tub settles faster and lower.

sim: { length: 60, dt: 0.1, unit: minutes, show: [water] }
```

## The top of the file

| Key | What it is |
| --- | --- |
| `id` | Short name, used in links between systems and in the page address. Lowercase letters, numbers and hyphens. Made from the title when missing. |
| `title` | What it's called. `name` works too. |
| `compression` | The whole system in one sentence: why it behaves the way it does. A good shape is "X, so Y, unless Z". `summary` and `gist` work too. |
| `question` | The question it answers, in the words of someone living inside it. |
| `behavior` | Its typical shape over time: `goal-seeking`, `overshoot and collapse`, `stuck`, `oscillates`, or a few plain words. |
| `scale` | Where it sits in the atlas, smallest to largest: `thing`, `personal` (or `life`), `relationship`, `team` or `company`, `industry` or `market`, `economy` or `society`, `planet`. |
| `note` | A paragraph of background. `description` works too. |
| `tags` | A list of words for the library. |
| `archetypes` | Ids of the traps this one is an instance of, like `[limits-to-growth]`. Matches are also found on their own. |
| `parent` | The id of a bigger system this one sits inside, when no part of that system names this one as its `child`. |

## elements

The parts. A map from id to details, or a list of ids. An id uses letters, numbers and underscores (`close_friends`); spaces and hyphens are turned into underscores. A bare string is just a label: `stress: Stress`.

| Key | What it is |
| --- | --- |
| `label` | The name shown on the map. Defaults to the id with spaces. |
| `kind` | What sort of part it is (below). Defaults to `var`, or to `flow` when there's a `rate`, or `stock` when there's a `start` or `change`. |
| `tier` | `1` the core, always shown; `2` more detail; `3` everything. The detail slider shows tier 1, then 2, then 3. `level` works too. Aim for six to twelve parts at tier 1. |
| `note` | A line or two on what it is, why it matters, or where a number came from. `description` and `why` work too. |
| `unit` | `people`, `$k`, `hours/week`, `GW`... |
| `child` | The id of another system that zooms in on this part. On the map the part gets a zoom badge; in the atlas the child sits inside its parent. |
| `shared` | A name for the same thing in other systems (`energy`, `attention`), so the atlas can thread them together. |
| `from`, `to` | Flows only: the stock it drains and the stock it fills. Leave one out for a flow that comes from or goes to outside the system. |
| `start`, `change`, `rate`, `eq`, `value`, `range`, `step`, `min`, `max`, `negative` | Numbers and equations for running it. See [Making it run](#making-it-run). |
| `pos` | `[x, y]` to pin a part in place on the map. Usually left out; dragging parts on the map saves a layout separately. |

### Kinds

| Kind | Means | Also written |
| --- | --- | --- |
| `stock` | Something that builds up or drains away: water, cash, trust, people, ideas. It changes only through flows. | `level`, `reservoir`, `accumulation` |
| `flow` | What fills or drains a stock, per unit of time. Give it `from:` and/or `to:`. | `rate` |
| `var` | A factor that goes up and down. The default. | `factor`, `variable`, `quantity`, `condition`, `feeling`, `emotion`, `knob`, `param`, `constant`, `aux` |
| `actor` | A person or group who decides things. | `person`, `agent`, `player`, `who` |
| `goal` | What someone, or the system, is aiming at. | `aim`, `purpose`, `target` |
| `rule` | A rule, incentive, norm or constraint. | `policy`, `norm`, `incentive`, `constraint`, `law` |
| `belief` | A mindset or story people act from. | `mindset`, `paradigm`, `story`, `value` |
| `event` | Something that happens once, like a shock or a decision. | `moment`, `shock` |
| `external` | Outside the system: it pushes in, but nothing inside pushes back. | `outside`, `exogenous`, `environment`, `context` |

Don't make everything a stock. Most parts of most systems are factors; stocks are the few things that remember.

## links

How the parts push on each other, one per line:

```yaml
links:
  - fish -> catch +            # more fish, more catch: same direction
  - price -> demand -          # higher price, less demand: opposite direction
  - profit -> boats + slow     # it takes seasons to build boats
  - worry -> avoidance -> worry + | one sign for every arrow on the line
  - rumor -> trust - maybe     # not sure this link exists
  - weather -> mood + weak
  - hype -> funding ?          # not sure which way it goes
```

After the names come optional flags, in any order:

| Flag | Means |
| --- | --- |
| `+` (or `same`, `s`) | Same direction: more of a, more of b. The default. |
| `-` (or `opposite`, `o`) | Opposite direction: more of a, less of b. |
| `?` | Not sure which way yet. |
| `+?`, `-?` | A guess at the sign. |
| `slow` (or `delay`, `delayed`, `later`, `lag`, `\|\|`) | There's a delay. Delays are where systems overshoot and oscillate. |
| `maybe` (or `unsure`, `guess`, `probably`) | Not sure the link exists at all. |
| `weak`, `strong`, or a number from 0 to 1 | How much of a push gets through. Used when you poke the map. |

A note goes after `|`. Name parts by their ids. A line with several arrows is a chain, and its flags apply to every arrow.

The long form works too, and is handy when a note has a `|` in it. Its `sign` is `same`, `opposite` or `?` (a bare `-` needs quotes in YAML):

```yaml
links:
  - { from: price, to: demand, sign: opposite, delay: true, note: people take a while to notice }
```

So does one block of text:

```yaml
links: |
  fish -> catch +
  catch -> fish -
```

Some links write themselves:

- A flow's `from:` and `to:` connect it to its stocks. An inflow raises its stock; an outflow lowers it.
- An equation draws links from every part it reads, with the sign worked out from the formula where possible. `min`, `max`, `clamp` and plain arithmetic give clear signs; `if()` and comparisons give `?`. A part read through `delay`, `smooth` or `lag` gets a slow link. A link you write yourself replaces the one an equation drew.

## loops

The circles that matter. Name them `R1`, `R2`... for reinforcing loops (they snowball, either way) and `B1`, `B2`... for balancing loops (they pull toward something). The reader checks the name against the signs, and warns when they disagree or when a step in the path isn't a link.

```yaml
loops:
  R1:
    label: Success breeds success
    path: [skill, wins, confidence, practice]
    note: Each win makes practicing easier, which makes the next win likelier.
```

`path` is the parts in order around the circle, following the links; it closes back on its first part by itself. `"a -> b -> c"` works as a path too. `name` works for `label`, `story` for `note`.

Loops nobody named are found anyway and offered as suggestions in the Loops lens. Name the two or three that explain the behavior.

## levers

Places to push. Each sits on one of Donella Meadows' twelve rungs, from the weakest place to intervene (12) to the strongest (1).

```yaml
levers:
  small_often:
    label: Publish small and often
    rung: 10
    at: [writing, stakes]
    set: { piece_size: 0.3 }
    note: Changes the unit of work, so each piece matters less and gets done.
    effort: 2
    reach: 5
    status: idea
```

| Key | What it is |
| --- | --- |
| `label` | What you'd do, as an action. |
| `rung` | Where it sits on the ladder, 12 to 1 (below). `leverage` works too. |
| `at` | The parts it pushes on. Left out, it's worked out from `set`, including the parts whose equations use a param it sets. `on` and `targets` work too. |
| `set` | For a file that runs: what it changes, as `{ name: number }` or `{ name: "equation" }`. Names can be params or parts. The Over time lens runs the system with and without it. |
| `adds` | Links it would add, written like any link (`[review -> spending -]`). Drawn on the map when the lever is picked. |
| `note` | Why it would work, or why it backfires. `why` works too. |
| `effort`, `reach` | How hard it is, and how much it changes, each from 1 to 5. `impact` works for `reach`. The Levers lens plots them. |
| `status` | `idea`, `trying`, `tried` or `working`. |

Include one lever that looks like the fix and backfires; it's often the most useful one.

`scenarios` take the same shape, for what-ifs about the world rather than things you'd do: a recession, a drought, a breakup. They don't need a rung.

### The rungs

| Rung | Name | What it changes |
| --- | --- | --- |
| 12 | Numbers | Constants and parameters: taxes, budgets, hours, how much of something |
| 11 | Buffers | The sizes of stabilizing stocks, relative to their flows |
| 10 | Stock-and-flow structure | The physical layout: which stocks exist and where things can flow |
| 9 | Delays | The lengths of delays, relative to how fast things change |
| 8 | Balancing loops | The strength of the self-correcting loops |
| 7 | Reinforcing loops | The gain of the runaway loops |
| 6 | Information flows | Who knows what, and when |
| 5 | Rules | Incentives, punishments, constraints |
| 4 | Self-organization | The power to add, change or evolve the structure |
| 3 | Goals | What the system is for |
| 2 | Paradigms | The mindset the goals, rules and structure come from |
| 1 | Transcending paradigms | Holding any mindset lightly |

## Making it run

Optional. A file runs when at least one stock has a `start`. Then:

- **Stocks** get `start:`, a number or an equation of params. They change through their flows, or through `change:`, a net change per unit of time, for models without flows. A stock stays at or above `min` (0 unless you say otherwise, or `negative: true`) and at or below `max`.
- **Flows** get `rate:`, how much moves per unit of time. A flow runs one way; a rate below zero moves nothing, so use two flows for something that goes both ways. A stock never drains below its `min`: when outflows would take more than is there, they shrink to fit.
- **Factors** get `eq:`, computed fresh every step. `equation` works too.
- **Knobs** are numbers you can turn while it runs. Put them in `params`, or give a part a `value`.

```yaml
params:
  social_hours: { label: Hours a week with friends, value: 12, range: [2, 30], unit: hours/week }
  drift_rate: { label: How fast close friends drift, value: 0.05, range: [0, 0.2], step: 0.005, unit: /month }
```

A param is `{ label, value, range: [low, high], step, unit, note }`; only `value` is needed. `low:` and `high:` work for `range`. A param can't share an id with a part.

Equations read parts and params by id, plus `t` (or `time`), `dt`, `pi` and `e`. A part an equation reads that has no number of its own counts as 0, with a warning. They use `+ - * / % ^`, comparisons and `&& || !` (true is 1, false is 0), `c ? a : b`, and these functions:

| Function | Does |
| --- | --- |
| `min(a, b, ...)`, `max(a, b, ...)`, `clamp(x, low, high)` | Limits. Prefer these to `if()`: they keep link signs clear. |
| `abs`, `exp`, `ln` (or `log`), `log10`, `sqrt`, `pow(x, y)`, `floor`, `ceil`, `round`, `sign`, `sin`, `cos`, `tanh` | The usual. `ln`, `log10` and `sqrt` give 0 where they'd otherwise fail. |
| `if(c, a, b)` | `a` when `c` is true, otherwise `b`. |
| `lookup(x, x1, y1, x2, y2, ...)` | A curve through points, straight between them, flat past the ends. |
| `step(height, at)` | 0, then `height` from time `at`. |
| `pulse(height, at, width)` | `height` for `width` time units starting at `at` (one step if `width` is left out). |
| `ramp(slope, from, until)` | Rises by `slope` per time unit from `from`, flat after `until`. |
| `wave(amplitude, period, phase)` | A sine wave over time. |
| `noise(amplitude, seed)` | A random wiggle between minus and plus `amplitude`, the same on every run. |
| `smooth(x, time, start)` | `x` as it's slowly noticed: catches up about two thirds of the way in `time`. |
| `delay(x, time, start)` | `x`, arriving over about `time`, like an order that takes a while. |
| `lag(x, time, start)` | Exactly `x` from `time` ago, like a pipeline. |

In a `{ }` map, YAML splits a value at each comma. The reader joins equations like `{ rate: min(a, b) }` back together, but quoting them (`rate: "min(a, b)"`) or writing them on their own line is safer, and labels with commas need quotes or their own line.

Two things to avoid. A ring of factors and flows that compute from each other with no stock in between has no order to compute in; break it with a stock, a param or a `smooth()`. And an equation that can run away to infinity usually needs a `min`, `max` or `clamp`.

```yaml
sim: { length: 24, dt: 0.1, unit: months, start: 0, show: [close, regular] }
```

`length` is how long to run (`until` and `duration` work too; 100 if left out). `dt` is the step; a sensible one is picked from the length. `unit` labels the time axis. `start` is the time at the start, for `t`, `step` and the rest. `show` picks what Over time charts first; it defaults to the stocks.

## story

A walk through the system, one step at a time, for the Story lens and Present mode.

```yaml
story:
  - say: About fifty close friends, on weekly and monthly rhythms.
    show: [close]
  - say: Each one needs a little time, and the hours run out.
    loop: B1
  - say: The strongest lever isn't more hours. It's making each hour hold more people.
    lever: group_plans
  - say: Squeeze the time and watch what moves.
    poke: capacity-
    level: 2
```

Each step has `say` (or `text`), and any of: `show` (or `focus`), the parts to light up; `loop`, a loop to trace; `lever`, a lever to point at; `poke: id+` or `poke: id-`, a push that ripples across the map; `level`, the detail tier to show, 1 to 3. Five steps is plenty.

## questions and sources

```yaml
questions:
  - What's the smallest thing I could publish this week?
sources:
  - Donella Meadows, Thinking in Systems (2008), chapter 1
  - More cities pressing pause on data centers, Stateline (May 2026) https://stateline.org/2026/05/28/more-cities-are-pressing-pause-on-data-centers-as-local-backlash-grows/
  - { label: "Leverage Points, Donella Meadows (1999)", url: "https://donellameadows.org/archives/leverage-points-places-to-intervene-in-a-system/" }
```

`questions` (or `open_questions`) are what's still unknown; the map is a guess until they're answered. A source is a line of text, with a link at the end if there is one, or `{ label, url }`.

## Nesting

Any part can open into a system of its own. Give the part `child: other-id`, and the other file is its closer look: the map shows a zoom badge (double-click the part to go in, Esc to come back out), the Outline unfolds it inline, and the atlas draws the child inside its parent. A child can name its parent with `parent:` instead, when no single part leads to it.

`shared` ties the same thing together across systems that aren't nested. A part with `shared: energy` in one file and another with `shared: energy` in a second are threaded together in the atlas, so you can see a stock that two parts of a life both draw on.

## Writing a good one

- Start from the behavior: what happens over and over, and what's puzzling about it. The `question` should be one a person living inside the system would ask.
- The `compression` explains the behavior through the structure, in one plain sentence.
- Keep tier 1 small. Six to twelve parts and two or three named loops are enough to explain most things; the rest is tier 2 and 3.
- Put the delays in. A `slow` link on the right arrow explains more overshoot than any number.
- Guesses go in `note` and `questions`. Don't invent facts about people.
- Levers go all the way up the ladder: a number, a rule, a goal, a mindset. Say which one backfires.
- For a file that runs, rough numbers are fine. Say where each one comes from in its `note`, and check the run against what really happens.
