Skip to content
chiltepin

Guide

Four diagrams distributed systems need

A service map tells you what talks to what. It does not tell you where the latency went, what runs backwards when step three fails, what guarantees the queue makes, or how far the new version has rolled out. Those are four different questions, and each has a shape.

All four are typed blocks: fenced YAML inside an ordinary Markdown file, validated by chiltepin check and rendered at build time. Nothing below is a screenshot — every figure on this page is the block’s own starter template, drawn by the same pipeline chiltepin build runs.

Where did the time go — a trace waterfall

One lane per service, a nice-number time axis, and one bar per span placed by its start and sized by its duration on a shared scale. Bars lighten as they nest, so depth is legible without indentation, and the critical path — the root, then the longest child at each hop — takes the accent. A span marked error: true gets a negative outline and an ERR chip rather than a colour you have to decode.

```spans
title: GET /orders/{id}
unit: ms
spans:
  - api/get: GET /orders/{id} · 0 · 120
  - api/auth: verify token · 4 · 10 · get
  - db/q1: SELECT orders · 18 · 40 · get
  - { id: cache, service: cache, name: "GET order:42", start: 62, duration: 3, parent: get, kind: cache }
  - { id: pay, service: payments, name: GET /payments/42, start: 68, duration: 46, parent: get, kind: client, error: true }
```
SECTION 01 · Trace

GET /orders/{id}

SPANS
Trace waterfall: 5 spans0 ms20 ms40 ms60 ms80 ms100 ms120 msapidbcacheCACHEpaymentsCLIENTGET /orders/{id}120 ms10 msverify tokenSELECT orders40 ms3 msGET order:42GET /payments/4246 msERR
Legendnested spancritical patherrorCACHEcacheCLIENTclient
A spans block. The accent follows the critical path, so the answer to “what made this request slow” is the thing your eye lands on first.

What runs backwards — a saga

Forward steps run left to right, each with the service that owns it as a chip and its compensating action as a dashed card underneath. failAt names the step that fails: it takes the accent and a FAILED chip, earlier steps read COMPENSATED, later ones SKIPPED, and a dashed compensating flow runs right to left back to the first step.

Because the failure is a field, the same document can show the happy path by deleting one line — and a step with no compensation is visible immediately, which is usually the bug.

```saga
title: Place order
mode: orchestration
coordinator: Order service
steps:
  - reserve: Reserve stock · inventory · release stock
  - charge: Charge card · payments · refund card
  - ship: Book shipment · shipping · cancel shipment
  - notify: Send confirmation · notifications
failAt: ship
```
SECTION 01 · Saga

Place order

SAGA
Saga: 4 steps, 3 compensationsOrder serviceORCHESTRATOR1inventoryCOMPENSATEDReserve stockCOMPENSATErelease stock2paymentsCOMPENSATEDCharge cardCOMPENSATErefund card3shippingFAILEDBook shipmentCOMPENSATEcancel shipment4notificationsSKIPPEDSend confirmation
LegendORCHESTRATORcoordinates every stepstepcompensationfailure pointCOMPENSATEDundoneskippednextnot reachedcompensating flow
A saga block with failAt: ship. Orchestration mode adds the coordinator band across the top.

What the queue promises — an event contract

The asynchronous twin of an HTTP endpoint card. It carries the channel, the producers and consumers, the delivery guarantee, the ordering guarantee, the partition key, the retention window, the payload schema, an example, and the errors a consumer should expect. The partition-key row is marked # in the payload table — the card’s one accent, because it is the field that decides ordering.

```eventcontract
name: order.placed
version: v2
channel: orders
summary: A customer completed checkout and the order is accepted.
producers: [checkout]
consumers: [billing, fulfilment, analytics]
delivery: at-least-once
ordering: per-key
key: order_id
retention: 7d
schema:
  - order_id uuid required — The order this event is about
  - customer_id uuid required — The buyer
  - total money required — Grand total after discounts
  - coupon string — Discount code applied, if any
headers:
  - trace_id string required — W3C trace id
example: |
  { "order_id": "ord_123", "customer_id": "cus_9", "total": "42.00 EUR" }
errors:
  - DuplicateOrder — the same order_id was already processed
note: Consumers must be idempotent on order_id.
```
SECTION 01 · Event contract
EVENT·v2order.placedchannelorders

A customer completed checkout and the order is accepted.

Producers (1)
checkout
Consumers (3)
billingfulfilmentanalytics
deliveryat-least-onceorderingper-keykeyorder_idretention7d
Payload
FieldTypeDescription
#order_iduuidThe order this event is about
customer_iduuidThe buyer
totalmoneyGrand total after discounts
?couponstringDiscount code applied, if any
# partition key · ? optional
Headers
FieldTypeDescription
trace_idstringW3C trace id
Example
{ "order_id": "ord_123", "customer_id": "cus_9", "total": "42.00 EUR" }
Errors
ErrorWhen
DuplicateOrderthe same order_id was already processed

Consumers must be idempotent on order_id.

An eventcontract block. Optional fields are marked ?; the partition key is marked #.

How far has it gone — a rollout

One card per stage with its traffic share, its hold time, and the gate it must pass to advance, riding as a chip on the connector to the next card. Status is drawn in form as well as colour: done is a filled card, current an accent outline, next a dashed one, blocked a negative one. The rollback move is the footer, because that is the line someone needs at 3am.

```rollout
title: Checkout v2
strategy: canary
stages:
  - "[done] 1% · Smoke · 15m — no 5xx"
  - "[current] 10% · Canary · 30m — error rate < 0.5%"
  - "[next] 50% · Half · 1h — p95 < 300ms"
  - "[next] 100% · Full"
rollback: Flip the flag off; the old version keeps serving.
```
SECTION 01 · Rollout

Checkout v2

ROLLOUTcanary
Stage 1
Smokedone
1%
15m
Stage 2
Canarycurrent
10%
30m
Stage 3
Halfnext
50%
1h
Stage 4
Fullnext
100%
LegenddonecurrentnextGATEgate — must pass to advance
RollbackFlip the flag off; the old version keeps serving.
A rollout block. strategy accepts canary, blue-green, rolling or feature-flag.

They compose with the rest of the document

None of these is a standalone picture. A runbook can carry the trace waterfall next to the endpoint card it belongs to; an ADR can put the saga beside the options table that chose it; a launch doc can hold the event contract, the rollout and the dashboard links in one file that exports as a page, a slide deck and a PDF.

The architecture block covers the deployment side: groups nest region inside zone inside subnet, nodes take a replicas count and draw as stacked cards, and preset: k8s frames a Kubernetes namespace map with the right chips and glyphs.

Frequently asked questions

How do you diagram a distributed trace?
As a waterfall: one lane per service, a shared time axis, and one bar per span placed by its start time and sized by its duration. Nesting depth is carried by fill rather than indentation, so a deep trace stays readable, and a thin connector joins each child to its parent. The spans block draws this from a list of id, name, start and duration values, and marks the critical path — the root, then the longest child at every hop — so the question “where did the time actually go” has a visible answer.
What is a saga diagram, and what should it show?
A saga is a transaction spread across services that cannot roll back with a single COMMIT, so each forward step carries a compensating action that undoes it. A saga diagram is only useful if it shows three things: the forward steps in order, the compensation under each, and what happens when one step fails. The saga block takes a failAt field naming the failing step; the renderer then marks earlier steps COMPENSATED, later ones SKIPPED, and draws the compensating flow running backwards to step one.
How do you document an asynchronous event, not an HTTP endpoint?
An endpoint card answers method, path, request, response. An event answers a different set: which channel it lands on, who produces and consumes it, the delivery guarantee (at-least-once, at-most-once, exactly-once), the ordering guarantee, which field is the partition key, and how long it is retained. The eventcontract block is the async twin of endpoint and carries exactly those fields, with the partition key marked in the payload table.
Can a rollout plan live in the docs rather than a ticket?
Yes, and it should, because the gates are the interesting part. A rollout block draws each stage with its traffic percentage, its hold time, the gate it must pass to advance, and its status — done, current, next or blocked — plus the rollback move as a footer. Because it is data, the current stage moves by editing one word in a pull request.
Why not draw these in a general diagramming tool?
You can, and the result is a picture nobody updates. A drawing tool has no idea what a critical path is, so it cannot mark one; no idea what a compensation is, so it cannot tell you a step is missing one; and no schema, so nothing fails when the diagram and the system disagree. A typed block gives chiltepin check something to validate, and gives the renderer enough structure to make the layout decisions itself.
How do these diagrams stay current?
They are text in the repository, so they change in the same pull request as the code and get reviewed as a diff. chiltepin check runs in CI and fails on a bad field, an unknown span parent, a saga step that references a missing compensation, or a duplicate id. An AI agent with the Chiltepin skill can draft the update; the schema is what stops it from inventing a shape that does not exist.