ModsCodeMERGEBLE LLC Search Claude Code Mods

ModsCode / Gallery / Designs / Subagent swimlanes

Subagent swimlanes

The main agent and its subagents as lanes over time: tool calls are segments on each lane, a failed call is red and a running call stays open to the NOW line.

Subagent swimlanesSubagent swimlanes
Work and agents · 380×232 · sample data
hooks/designs/agent-lanes.mjs
// agent-lanes: one Claude Code task as swimlanes, the main agent on top and five subagents below it in
// the order they were spawned, over a 12-minute window. Each lane's life is gantt's 8px track at 14% ink
// and every tool call is a segment on it at candle's 55% fill, so the busy stretches read as solid ink
// and the waits as track. The main lane's short calls line up with the start of each subagent (the
// Task call that spawned it), which shows the hand-offs without drawing links across other lanes.
// 40 calls in all: main 8 (hand-placed), the subagents 7 / 5 / 9 / 5 / 6 from a seeded split of their
// span. One test-runner call failed (npm test, exit 1) and is the one red segment; the reviewer's last
// call (54s so far) is still running and stays open up to the NOW line at 11m 48s, the one Claude orange item. A finished
// subagent ends in a 2px up-coloured dot. Times are minutes from the start, as plain numbers.
// Sample data only.
const W = 380
const H = 232
const STAGE = '#FAF9F5'
const INK = '#141413'
const MUTED = '#73726C'
const UP = '#2C7A39'
const DOWN = '#AB2B3F'
const CLAUDE = '#D77757'
const FONT = "'Inter Tight','Segoe UI',system-ui,sans-serif"
const f = n => n.toFixed(1)

const PLOT = { left: 96, right: 368 } // the names and call counts sit in the column before it
const MINUTES = 12
const NOW = 11.8 // 11m 48s
const LANE0 = 54 // the centre line of the main lane
const PITCH = 27
const BAR = 8
const TICK_Y = 202 // the minute stubs; their labels share the caption line

// Seeded so the picture is the same on every render
function rng(seed) {
  return () => (seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff) / 0x7fffffff
}

const text = (x, y, s, { size = 9, fill = MUTED, anchor = 'start' } = {}) =>
  `<text x="${f(x)}" y="${f(y)}"${anchor === 'start' ? '' : ` text-anchor="${anchor}"`} font-size="${size}" font-family="${FONT}" fill="${fill}">${s}</text>`

const x = m => PLOT.left + ((PLOT.right - PLOT.left) * m) / MINUTES

// The main agent's calls in minutes; the ones ending at 0.6, 2.0, 4.2, 5.0 and 7.5 are the Task calls
// that spawn each subagent, so they end where the subagent's lane begins.
const MAIN = [[0.05, 0.35], [0.45, 0.6], [1.2, 1.5], [1.85, 2.0], [4.05, 4.2], [4.85, 5.0], [7.3, 7.5], [9.3, 9.8]]
const SUBS = [
  { name: 'explore', start: 0.6, end: 3.4, calls: 7 },
  { name: 'plan', start: 2.0, end: 4.6, calls: 5 },
  { name: 'test-runner', start: 4.2, end: 9.0, calls: 9, failed: 4, note: 'npm test, exit 1' },
  { name: 'docs', start: 5.0, end: 8.2, calls: 5 },
  { name: 'reviewer', start: 7.5, end: NOW, calls: 6, running: 10.9 }, // the open call began at 10m 54s
]

// Splits a lane's span into calls and the waits between them. A call weighs 0.5 to 1.5 and a wait 0.3
// to 1.5, so calls are a little longer than gaps on average; the lane starts and ends with a call.
function split(lane, rnd) {
  // A running lane keeps its open call whole and splits only the time before it
  if (lane.running) return [...split({ ...lane, end: lane.running - 0.3, calls: lane.calls - 1, running: 0 }, rnd), [lane.running, lane.end]]
  const calls = Array.from({ length: lane.calls }, () => 0.5 + rnd())
  const waits = Array.from({ length: lane.calls - 1 }, () => 0.3 + rnd() * 1.2)
  const scale = (lane.end - lane.start) / [...calls, ...waits].reduce((s, v) => s + v, 0)
  const segs = []
  let t = lane.start
  calls.forEach((c, i) => {
    segs.push([t, t + c * scale])
    t += c * scale + (waits[i] ?? 0) * scale
  })
  return segs
}

function draw() {
  const rnd = rng(20261025)
  const lanes = [{ name: 'main', start: 0, end: NOW, segs: MAIN }, ...SUBS.map(s => ({ ...s, segs: split(s, rnd) }))]
  const total = lanes.reduce((n, l) => n + l.segs.length, 0)
  const done = SUBS.filter(s => !s.running).length
  const parts = [`<rect width="${W}" height="${H}" rx="4" fill="${STAGE}"/>`]

  // Header: the call count on the left, a two-item key on the right (both counted from the data)
  parts.push(text(12, 24, String(total), { size: 13, fill: INK }))
  parts.push(text(12 + String(total).length * 8 + 4, 24, 'TOOL CALLS'))
  parts.push(`<rect x="320" y="18" width="6" height="6" fill="${DOWN}" fill-opacity="0.55"/>`)
  parts.push(text(W - 12, 24, `${SUBS.filter(s => s.failed !== undefined).length} FAILED`, { anchor: 'end' }))
  parts.push(`<circle cx="272" cy="21" r="2" fill="${UP}"/>`)
  parts.push(text(279, 24, `${done} DONE`))

  lanes.forEach((lane, i) => {
    const cy = LANE0 + i * PITCH
    const top = cy - BAR / 2
    parts.push(text(12, cy + 3.5, lane.name, { size: 10, fill: INK }))
    parts.push(text(PLOT.left - 8, cy + 3, String(lane.segs.length), { anchor: 'end' }))
    // The lane's life: from spawn to finish (or to now) as a track
    parts.push(`<rect x="${f(x(lane.start))}" y="${f(top)}" width="${f(x(lane.end) - x(lane.start))}" height="${BAR}" rx="1" fill="${INK}" fill-opacity="0.14"/>`)
    lane.segs.forEach(([a, b], k) => {
      const failed = k === lane.failed
      const w = Math.max(2, x(b) - x(a))
      parts.push(`<rect x="${f(x(a))}" y="${f(top)}" width="${f(w)}" height="${BAR}" rx="1" fill="${failed ? DOWN : INK}" fill-opacity="0.55"/>`)
      if (failed) parts.push(text(x(a), top - 4, lane.note, { fill: DOWN }))
    })
    if (lane.running) {
      parts.push(text(x(NOW) - 4, top - 4, `RUNNING ${Math.round((NOW - lane.running) * 60)}s`, { anchor: 'end' }))
    } else if (i > 0) {
      parts.push(`<circle cx="${f(x(lane.end) + 5)}" cy="${f(cy)}" r="2" fill="${UP}"/>`)
    }
  })

  // Minute stubs every minute, labels every two, on the caption line like bars' hours
  for (let m = 0; m <= MINUTES; m++) {
    if (Math.abs(x(m) - x(NOW)) < 6) continue // a stub this close to NOW reads as a doubled line
    parts.push(`<line x1="${f(x(m))}" x2="${f(x(m))}" y1="${TICK_Y}" y2="${TICK_Y + 4}" stroke="${MUTED}" stroke-width="1"/>`)
    if (m % 2 === 0 && m < MINUTES) parts.push(text(x(m), H - 8, m ? `${m}m` : '0', { anchor: 'middle' }))
  }
  // The NOW line runs over the lanes so it stays visible where it crosses the running call
  parts.push(`<line x1="${f(x(NOW))}" x2="${f(x(NOW))}" y1="40" y2="${TICK_Y + 4}" stroke="${CLAUDE}" stroke-width="1" stroke-dasharray="3 3"/>`)
  parts.push(text(x(NOW), H - 8, 'NOW', { fill: CLAUDE, anchor: 'middle' }))
  parts.push(text(12, H - 8, 'SAMPLE AGENTS'))
  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">${parts.join('')}</svg>`
}

let source // drawn on first use and kept: the pane loads every design, so none pays at load time
export default {
  id: 'agent-lanes',
  title: 'Subagent swimlanes',
  category: 'work',
  description: 'The main agent and its subagents as lanes over time: tool calls are segments on each lane, a failed call is red and a running call stays open to the NOW line.',
  element: ui => ui.Svg({
    source: (source ??= draw()),
    alt: `Swimlanes of the main agent and ${SUBS.length} subagents over ${MINUTES} minutes, each tool call a segment, a failed call in red and the running call open to the NOW line, sample data`,
    width: W,
    height: H,
  }),
}

To use it in a mod: copy the file to your mod's hooks/designs/ and return its element(ui) from a pane's ui.render. How a pane is built.

Code: PolyForm Noncommercial License 1.0.0: noncommercial use only. Required Notice: Copyright 2026 Haruki Nakada (https://modscode.com).