# Sequence Diagram For multi-actor protocols and conversations — "who talks to whom, in what order". A row of actor headers at the top, dashed lifelines dropping down, and numbered horizontal message arrows between them. The classic UML sequence diagram, adapted to this skill's design system. ## When to use - Authentication and authorization flows — OAuth, OIDC, SAML, Kerberos, JWT refresh, mTLS handshakes - Wire protocols — TCP three-way handshake, TLS handshake, WebSocket upgrade, HTTP/2 multiplex - RPC patterns — gRPC unary and streaming, JSON-RPC, GraphQL over websockets - Webhooks, callbacks, and event dispatch — stripe webhooks, GitHub events, PubSub fan-out - Any "request + response" dance between 2–4 named services where *order* and *who initiates what* is the point **Trigger verbs**: "protocol", "handshake", "auth flow", "request/response", "exchange between", "round trip", "who calls what", "API dance". **When not to use**: - Single-actor sequential process → `flowchart.md` ("what steps does the build pipeline run") - Containment / architecture / "what lives inside what" → `structural.md` - "Feel how X works" / mechanism intuition → `illustrative.md` **Routing test**: if the prompt names ≥2 distinct actors/participants/services (User + Server, Client + Auth + Resource, Browser + CDN + Origin), prefer sequence even when the noun is "flow" or "process". Single-actor "X flow" stays flowchart. ## Planning before you write SVG 1. **List the actors**, 2–4 of them, in the order they appear across the page (usually left = initiator, right = downstream services). Each actor needs a short title (≤12 chars for N=4) and an optional single-line role subtitle. 2. **Assign one color ramp to each actor**. Default palette: `[c-gray, c-teal, c-purple, c-blue]` left-to-right. Swap in `c-coral`, `c-pink`, `c-amber`, `c-green` as needed — pick cool/neutral ramps and avoid semantic collisions (green for success, red for error) unless the actor literally means that. 3. **List the messages** as ordered tuples `(sender, receiver, short label)`. Cap at 10 messages — 6–8 is the sweet spot. If your protocol has more, split into an overview diagram and one detail diagram per sub-flow. 4. **Mark self-messages** — messages that stay on the same lifeline (e.g., "validate token" on the resource server). Limit to 1–2 per diagram. 5. **Plan a side note** — a short title that names the protocol. For N ≤ 3, place it at top-left beside the first actor header. For N ≥ 4, place it at bottom-left under the lifelines. 6. **Check every message label** against its lane span using the budget formula in `layout-math.md`. Shorten aggressively — labels that barely fit today will overflow if you tweak the actor order. Save the plan to `plan.md` alongside the SVG so the next iteration can read it. ## Actor headers Each actor header is a two-line node (title + role) or a single-line node (title only), wrapped in a `c-{ramp}` group. Height is always 48, y=40..88. Single-line (title only): ```svg User ``` Two-line (title + role): ```svg Client app Web ``` Rules: - Header width matches `header_w` from `layout-math.md` (e.g., 120 for N=4). - Header x = lane center − header_w/2. - Title `y = 60` when role is present, `y = 64` when title-only (centered in the 48px box). - Role `y = 78`, always `ts`. - Sentence case. "Auth server", not "Auth Server". ### Pill-shaped actor header (optional) An actor that represents an **external participant** — `Human`, `User`, `Environment`, an input source, an output sink — can use a pill-shaped header (`rx = height / 2 = 24`) instead of the default rounded rect. The capsule shape visually separates "outside the system" from "inside the system" without using color. ```svg Human ``` Rules: - **Only for external actors.** Internal services (Auth server, Client app, Resource server) keep the `rx="8"` rectangle. Mixing pills and rects in the same diagram is fine as long as the distinction tracks "external vs internal" — if every actor is a pill, drop them all back to rectangles. - **Height is still 48.** `rx` must equal `height / 2 = 24`. Don't adjust the height. - **No role subtitle** inside a pill. The capsule is for actors whose name is self-explanatory (Human, Environment). If you need a subtitle, you want a rectangle, not a pill. - **At most 2 pills per diagram.** More than that and the visual contrast between external and internal actors disappears. ## Lifelines One `` per actor, using `class="lifeline"`, from y=92 down to the tail below the last message. ```svg ``` All lifelines share the same y2. Compute once: `y2 = last_arrow_y + 24`. ## Messages Each message is a single `` at its row's `arrow_y`, with a `ts` label above. The stroke class encodes the sender's actor ramp. ```svg 1. Click login 3. Show consent screen ``` Rules: - Label `y = arrow_y − 10`, centered between sender and receiver x. - Arrow `y1 = y2 = arrow_y`. - Arrow endpoints are offset ±6 from the lifeline x so the arrowhead doesn't touch the dashes: - Left-to-right: `x1 = sender_x + 6`, `x2 = receiver_x − 6` - Right-to-left: `x1 = sender_x − 6`, `x2 = receiver_x + 6` - Stroke class = `arr-{sender_ramp}` — `arr-gray` for User, `arr-teal` for Client app, etc. Never use `class="arr"` (the default mono-gray arrow class) on a sequence message. - Label text is sentence case with a numeric prefix: `"1. Click login"`, `"3. Show consent screen"`. Numbers make the reading order unambiguous when arrows cross. - Keep labels short — verify every label fits its lane span using the budget formula in `layout-math.md`. ## Self-messages A self-message stays on one actor's lifeline. Draw it as a small 16×24 rect on the lifeline, colored in that actor's ramp, with the label to the **left** (so it stays inside the diagram even for the rightmost lane). ```svg 9. Validate token ``` Rules: - `rect_x = lifeline_x − 8` (rect is 16 wide, centered on the lifeline). - `rect_y = arrow_y − 12` (rect is 24 tall, centered on the arrow row). - Label `x = rect_x − 8`, `text-anchor="end"`, `dominant-baseline="central"`, `class="ts"`. - Label still gets its numeric prefix (`"9. Validate token"`), following the overall message order. - Limit self-messages to 1–2 per diagram — more than that and the metaphor of "an actor talking to itself" stops reading clearly. ## Message frames A **frame** is a dashed rounded rect that visually groups a contiguous run of messages under a single condition — *"Until tasks clear"*, *"Iterative research loop"*, *"Only if authenticated"*. It reads like a bracket around the enclosed messages, not like a new container. Anthropic's house-style sequence diagrams use frames for repeating or conditional sub-flows that don't warrant their own diagram. ```svg Until tasks clear ``` Rules: - **Dashed rounded rect**, `rx="8"`, 1px stroke in the muted gray (`#B4B2A9` light / `#5F5E5A` dark — match `.lifeline`). Use inline `stroke-dasharray="5 4"` rather than a new CSS class. - **Horizontal span**: `x = leftmost_lane_x − 20`, `width = rightmost_lane_x − leftmost_lane_x + 40`. The frame stretches 20px past the outermost lanes it covers, so the label has clear air at the corner. - **Vertical span**: `y = first_arrow_y − 12`, `height = last_arrow_y − first_arrow_y + 24`. Tight enough that the frame feels like a bracket, not a new section. - **Label position**: top-left inside the frame, at `(frame_x + 10, frame_y + 12)`, `class="ts"`, `text-anchor="start"`, `dominant-baseline="central"`. Keep the label ≤24 characters. - **No numeric prefix** on the frame label — numbering is for individual messages. The frame is meta. - **At most 2 frames per diagram**, and **frames never nest**. Two frames can sit one above the other, but they cannot contain each other. If you need nested scoping, split into two diagrams. - **Draw frames BEFORE the messages** in source order so the dashed border sits behind the arrows and labels. When to use a frame: - A **loop** — "Until tests pass", "Until tasks clear", "Iterative research loop" - A **conditional block** — "Only if authenticated", "When cache miss" - A **named sub-protocol** — "Token refresh", "Retry with backoff" Don't use a frame to decorate a single message, and don't use one to group messages that have nothing in common — frames are a scoping tool, not a visual group. ## Side note A titled annotation for the protocol itself. Uses `class="box"` with a `th` title and an optional `ts` subtitle. Top-left (for N ≤ 3): ```svg OAuth 2.0 Authorization code flow ``` Bottom-left (for N ≥ 4, because the top row is filled with actor headers): ```svg OAuth 2.0 Authorization code flow ``` Budget: title ≤24 chars, subtitle ≤32 chars. The box is 240 wide, padding 20 per side → usable width 200 → ~24 `th` chars or ~28 `ts` chars. ## Layout patterns ### 2-actor request/response (N=2) User asks, server responds. Two lanes at x=185 and x=495. Typical message count: 2–4. Side note at top-left. ### 3-leg handshake (N=2, M=3) Classic TCP. Same layout as above, three messages going SYN → SYN-ACK → ACK. Color the initiator arrows in one ramp and the responder arrows in another — the alternation reads as the handshake. ### 4-actor OAuth-style flow (N=4, M=8–10) User, Client app, Auth server, Resource server. The canonical shape. Lane centers at 100, 260, 420, 580. Side note at bottom-left. One self-message on the Resource server for the "validate token" step. ### Async webhook (N=2 or 3) Sender fires an event, receiver acknowledges later. Often has a gap in the middle that you can skip — no need to draw "wait 30 seconds" as a message; just put consecutive messages in the natural reading order. ## Parallel independent rounds A sub-pattern for topics where **multiple independent call/response rounds** happen in sequence but share no lifeline state — each round is its own contained exchange, and the diagram's job is to contrast the structure of the rounds rather than track continuous state on a lifeline. Image #12 (Tool calling vs. Programmatic tool calling) is the canonical case. **When to use.** - Comparing tool-call vs. programmatic-script patterns where each round is an independent call/response. - Showing "N separate rounds of the same exchange" to communicate volume or variety. - The right half of a 2-up comparison where each side shows a different protocol style. **When not to use.** - There's genuine shared state between rounds (conversation history, pending requests) — that's a regular sequence with full lifelines. - There are only 2 actors and 2–4 messages — that's a regular 2-actor sequence. - The rounds are conditional ("if X then do round 2") — that's a frame on a regular sequence. ### Geometry — stacked rounds variant Each round is a **row-sized mini-exchange** between a fixed source on the left and a round-specific action box on the right. Rounds stack vertically with no shared lifeline. See `layout-math.md` → "Parallel rounds geometry". | Element | Coordinates | |--------------------|-------------------------------------------------| | Source actor | `x=60 y=120 w=120 h=60` (persistent on left) | | Round 1 action | `x=340 y=100 w=160 h=48 rx=6` | | Round 2 action | `x=340 y=180 w=160 h=48 rx=6` | | Round 3 action | `x=340 y=260 w=160 h=48 rx=6` | | Row pitch | 80 (round_y increases by 80 per round) | The source actor is drawn **once** at the top-left, not repeated per round. Its box height is just tall enough to span the first round's arrow row, with no lifeline extending below it — the absence of a lifeline is the signal that the source isn't carrying state across rounds. **Why no lifeline.** A lifeline would visually suggest that state persists; in this pattern each round is a fresh stateless call. The source box itself is the anchor — the left edge of every arrow starts at `source_right_edge`, not at a lifeline x. **Arrow pairs per round.** Each round is two arrows: ```svg 1. Call tool A 2. Response ``` Round k's arrow y values: `call_y = 100 + 24 + (k-1) × 80`, `response_y = call_y + 10`. Action box for round k: `y = call_y − 24`. **Action box colors.** Each round's action box **can** take a different ramp — this is the only place in a sequence diagram where color is allowed to cycle across messages. The reason: each round calls a *different tool* (file write, api call, db query), and the color communicates that variety. Use up to 3 different ramps; more than that and the reader can't remember what each ramp means. Source actor ramp stays **constant** across all rounds (usually `c-gray` — "Claude" or "Agent"). ### Script-wrapper variant The right half of image #12 uses the same stacked-rounds shape but wraps **multiple action boxes inside a single tall script rect** that represents an inline program executing several tool calls. Uses the `script-icon` pattern from `glyphs.md`. | Element | Coordinates | |----------------------|-----------------------------------------| | Source actor | `x=60 y=120 w=120 h=60` | | Script wrapper | `x=240 y=100 w=140 h=260 rx=6`, `c-gray`| | `{ }` label in wrapper | `(310, 124)`, `th` | | Divider line | `(256, 140) → (364, 140)`, `arr` | | Wrapped action 1 | `x=252 y=152 w=116 h=48 rx=4` | | Wrapped action 2 | `x=252 y=212 w=116 h=48 rx=4` | | Wrapped action 3 | `x=252 y=272 w=116 h=48 rx=4` | | External action | `x=440 y=200 w=160 h=48 rx=6` (optional)| The script wrapper replaces the individual action boxes with a single container that contains N smaller colored bands. Only the **script wrapper itself** gets arrows from the source — the inner bands are decorative, showing what's inside the script without each needing its own call arrow. ```svg run script ``` If the script calls an external tool (image #12's right half: a `file_system` external box on the far right), draw a single arrow from the script's right edge to the external box at the script's middle y. ### Side-by-side comparison The most common use of parallel rounds is inside a 2-up structural subsystem layout (`structural.md` → "Subsystem architecture pattern"): left container shows the stacked-rounds variant, right container shows the script-wrapper variant. Each sibling container is 315 wide, so halve the coordinates above: - Source actor inside container: `x = container_x + 20, w=100 h=50` - Action box inside container: `x = container_x + 180, w=120 h=40` - Round pitch inside container: 64 See `structural.md` → "Rich interior for subsystem containers" for how this interior type registers. ### Rules - **No lifelines.** If you draw lifelines, you're describing a regular stateful sequence, not parallel independent rounds. Drop them. - **Source actor appears once.** Never duplicate the source per round — that multiplies the visual noise and breaks the "same caller, different calls" intuition. - **Numeric prefixes still apply.** Within each round the two arrows are numbered (`1. Call tool A`, `2. Response`), and the numbering resets per round (or continues across rounds, at your choice — document it in the side note). - **≤3 rounds per diagram.** 4+ rounds runs out of vertical space and overwhelms the comparison. Use two diagrams if you need to show more. - **No frames around rounds.** A frame is a scoping bracket for a loop or a condition; parallel rounds are already independent, so a frame would be redundant. - **Action ramps cap at 3.** Each round can take a distinct ramp, but stop at 3 distinct colors. ## Rules of thumb - **Max 4 actors (comfortable), max 6 actors (absolute ceiling).** 5 is allowed only when every title is ≤12 chars and every subtitle is omitted. 6 requires every title ≤9 chars (so it fits the 100-wide header rect) and no subtitles, and the diagram loses its 40px horizontal safe margin — see `layout-math.md` → "Swim-lane (sequence) layout" for the full N=5 and N=6 coordinate tables. Beyond 6, split into an overview diagram plus one detail diagram per sub-flow — a 7-actor sequence diagram is guaranteed to feel cramped no matter how carefully you pack the labels. - **6–10 messages is the sweet spot.** 4 is too sparse (use a flowchart); 12+ is too dense (split). - **One ramp per actor**, up to 4 total. The default `[gray, teal, purple, blue]` works for most protocols. Swap ramps only when a topic needs it. - **Number every message.** Even "obvious" single-step diagrams benefit from "1. ...". Readers scan numbers before they read words. - **Every arrow gets a label.** Sequence diagrams are the one place in this skill where arrow labels are mandatory, not optional — the label *is* the message, and without it the diagram says nothing. - **Sender's ramp colors the arrow.** Never use `class="arr"` (mono-gray) for a sequence message. - **Labels above the line, never on it.** Labels float 10px above the arrow at the row midpoint, `text-anchor="middle"`, `class="ts"`. - **No crossings within a single row.** Two messages at the same `arrow_y` collide — always move one to a new row. - **No arcs, no curves.** Flat horizontal lines only. Self-messages use a labeled rect, not a small arc.