Authoring topologies

How to turn a real network into Marrow YAML — identifiers, devices, links, ports, status, and annotations.

A topology file has four parts: a header, zones, devices, and links. Everything else is detail hung off those. The complete key-by-key listing is in the schema reference; this page is about judgment.

Start from the story

Before writing YAML, decide what the diagram is for. The same site produces very different files depending on the answer:

  • "Where does traffic go?" → tiers on every device, links carry speed and kind.
  • "What's in each building?" → zones per site, tiers secondary.
  • "How is it segmented?"mode on links: trunk/access VLANs, routed L3, VPN tunnels, LACP bundles.
  • "What's broken right now?"status on devices and links, notes explaining each one.
  • "What does this branch look like?" → hub-and-spoke, spokes connected only to the hub.

Marrow renders what you declare. A diagram that tries to answer all four at once answers none of them well.

Identifiers

Every id is kebab-case: [a-z0-9._-], starting with a letter or digit. They must be unique across devices, and separately unique across zones.

id: core-sw-01        # good
id: dc1.core.sw01     # good — dots and underscores are allowed
id: Core SW 01        # error MRW-E012 — uppercase and spaces

Pick a convention and hold it. IDs appear in link endpoints, in ha_pair_with, in URLs on multi-page sites, and in every diagnostic — a consistent scheme is the difference between a readable diff and an unreadable one.

name: is the human label and is free text. If you omit it, the id is used.

Devices

Only id and icon are truly required, but a device with nothing else declared gives the engine nothing to work with:

devices:
  - id: core-sw-01
    name: Core Switch 1
    icon: cisco/switches/layer-3-switch   # required — search, never guess
    tier: core                            # core|distribution|access|edge|endpoint|management
    zone: campus                          # must be a declared zone id
    role: L3 core                         # free text, shown in the inspector
    status: up                            # up|down|degraded|planned|unknown
    mgmt_ip: 10.0.0.1
    ha_pair_with: core-sw-02              # declare on BOTH devices
    note: Change freeze until Q3
    config: |
      hostname core-sw-01
      ip routing
    interfaces:
      - name: Gi1/0/1
        description: uplink to dist-01
        vlan: 10
        ip: 10.0.0.1

note and config are the two keys that pay for themselves later. They show in the inspector, and on large topologies a device carrying either one earns its own dedicated page in the built site.

A link is from and to, each device-id:Port or bare device-id:

links:
  - from: core-sw-01:Gi1/0/1
    to: acc-sw-01:Gi0/48
    kind: fiber                # PHYSICAL: copper|fiber|dac|wireless|virtual|wan|serial
    speed: 10G                 # 10M|100M|1G|2.5G|5G|10G|25G|40G|100G|400G
    mode: trunk                # LOGICAL: access|trunk|routed|tunnel|peering|stack
    vlans: "10,20,30"          # what the trunk carries (native_vlan: N for the untagged one)
    status: up
    animated: true
    note: packet loss 12%

Only the first colon splits device from port. core-01:Gi1/0/1 parses as device core-01, port Gi1/0/1 — the slashes are part of the port name, which is what you want. An id containing a colon cannot be expressed; ids are kebab-case so this never comes up.

Two axes: the cable, and what it carries. kind/speed describe the physical link; mode describes its logical function. They compose — a 10G fiber cable can be an access port, a trunk, or a routed L3 link. speed drives stroke width; status drives colour; mode drives the symbol:

mode:SymbolCompanion keys
accessplain linevlans (the single VLAN)
trunkdouble railvlans, native_vlan
routedsolid + protocol chipprotocol
peeringdottedprotocol (ospf/ebgp/…)
tunneldashed + 🔒encap (ipsec/gre/vxlan/…)
stackthick bar

Like structure, meaning is declared, never guessed — the engine will not infer trunk from a port name. The exact VLANs/encap/protocol ride a small chip so the pattern stays uncluttered. Mismatched attributes (e.g. encap on a non-tunnel link) are advisory MRW-W105 warnings, not errors.

LAGs. Give the members a shared lag id — they bundle onto one strand chipped with that id (PO1); add lacp: active|passive|on|static|pagp to name the control protocol. Aggregation is orthogonal to mode, so a port-channel can be a trunk, an access, or a routed bundle. Use lag for port channels only, not as a general grouping key.

animated: true draws a flowing dash. It's loud on purpose. One or two per diagram to mark the path that matters; a diagram where everything is animated communicates nothing.

Status

status is a declaration in the file, not a live reading. Marrow never polls anything.

ValueDeviceLink
up (default)green LEDnormal stroke
downred LEDred stroke
degradedamber LEDamber, long dashes
plannedgrey LEDgrey, dotted, rounded caps
unknowngrey LED— (devices only)

If you want live status, generate the YAML from your monitoring system on a schedule and rebuild. That's a legitimate and common pattern — the file is the interface.

Ports and interfaces

Two different things, easy to conflate:

  • Interfaces (interfaces: on a device) are documentation. They appear in the inspector and on device pages.
  • Ports (the part after the colon in a link endpoint) are geometry. Marrow places a port chip on the device's edge and routes the cable to it.

They don't have to match — a link may use a port you never listed under interfaces — but a topology where they agree produces a far more useful inspector.

Reusing the same port on two links raises warning MRW-W101. That's usually a copy-paste mistake, occasionally a legitimate shared physical port.

Hero, theme, description

marrow: 2
name: Acme HQ
description: Core + distribution, three buildings   # watermark subtitle
theme: dark            # dark (default) | light
hero: core-sw-01       # the camera features this device on open

hero sets where the reader's eye lands first. Choose the device the diagram is about. You can also set hero: true on a device rather than naming it at the top; do one or the other.

Private annotations

Any key prefixed with x- is preserved and ignored:

devices:
  - id: core-sw-01
    icon: cisco/switches/layer-3-switch
    x-owner: netops
    x-asset-tag: A-99213
    x-cmdb-id: CI0004421

Unknown keys without the prefix are errors (MRW-E005). That is deliberate — a silently ignored typo like teir: core would quietly change your layout. See ADR-0006.

A checklist before you say it's done

  1. validate passes with zero errors.
  2. You have looked at a snapshot or the live preview.
  3. Every device that matters has a tier or a zone — meaning is declared, not implied by its name.
  4. ha_pair_with is declared on both sides of each pair.
  5. No credentials, no secrets, no real management passwords anywhere in the file.
  6. Warnings are either fixed or understood.