Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env python3
"""Generate the README diagrams with measured text.
Run: scripts/make-diagrams
Hand-placed SVG coordinates went wrong the obvious way: labels ran past the
right edge, captions landed on top of boxes, self-call arcs overlapped their
own text. All of those are the same bug — guessing how wide a string will be.
So nothing here is guessed. `text_width` estimates from a per-character table,
every box is sized to its content, every caption reserves its own space, and
`check_bounds` fails the build if any element ends up outside the canvas. A
diagram that does not fit is a broken diagram; better to find out here than in
a screenshot.
"""
from __future__ import annotations
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
OUT = REPO / "docs" / "architecture"
FONT = "Poppins, Avenir Next, Segoe UI, Helvetica, Arial, sans-serif"
# Relative advance widths for a geometric sans at size 1.0. Measured off
# Poppins; close enough for Avenir Next and Segoe UI that layout stays sane
# when a reader lacks the first font.
_NARROW = "iljt.,:;'|![]()/\\ "
_WIDE = "mwMW@%"
_CAPS = "ABCDEFGHIJKLMNOPQRSTUVXYZ0123456789"
def text_width(s: str, size: float, bold: bool = False) -> float:
total = 0.0
for ch in s:
if ch in _NARROW:
f = 0.30
elif ch in _WIDE:
f = 0.92
elif ch in _CAPS:
f = 0.66
elif ch in "—–↓→≥·":
f = 0.80
else:
f = 0.56
total += f * size
return total * (1.045 if bold else 1.0)
class Canvas:
def __init__(self, width: int, height: int, bg: str = "#ffffff"):
self.w, self.h = width, height
self.parts: list[str] = []
self.bg = bg
self.violations: list[str] = []
self.boxes: list[tuple] = []
def check(self, x0: float, y0: float, x1: float, y1: float, what: str) -> None:
if x0 < 0 or y0 < 0 or x1 > self.w or y1 > self.h:
self.violations.append(
f"{what}: ({x0:.0f},{y0:.0f})-({x1:.0f},{y1:.0f}) outside 0,0-{self.w},{self.h}"
)
def note_box(self, x, y, w, h, name):
"""Register a solid box so overlapping ones are caught."""
for (bx, by, bw, bh, bn) in self.boxes:
if x < bx + bw and bx < x + w and y < by + bh and by < y + h:
self.violations.append(f"{name} overlaps {bn}")
self.boxes.append((x, y, w, h, name))
def rect(self, x, y, w, h, fill, rx=0, stroke=None, sw=2):
s = f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{rx}" fill="{fill}"'
if stroke:
s += f' stroke="{stroke}" stroke-width="{sw}"'
self.parts.append(s + "/>")
self.check(x, y, x + w, y + h, "rect")
def text(self, x, y, s, size, fill, anchor="start", bold=False, what="text"):
weight = ' font-weight="700"' if bold else ""
esc = s.replace("&", "&").replace("<", "<").replace(">", ">")
self.parts.append(
f'<text x="{x}" y="{y}" font-size="{size}" fill="{fill}"'
f' text-anchor="{anchor}"{weight}>{esc}</text>'
)
w = text_width(s, size, bold)
x0 = x if anchor == "start" else (x - w / 2 if anchor == "middle" else x - w)
self.check(x0, y - size, x0 + w, y + size * 0.3, f"{what}: {s[:40]!r}")
def line(self, x1, y1, x2, y2, stroke="#6f6f6f", sw=2.1, arrow=True, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
a = ' marker-end="url(#arw)"' if arrow else ""
self.parts.append(
f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{stroke}"'
f' stroke-width="{sw}"{d}{a}/>'
)
def path(self, d, stroke="#6f6f6f", sw=2.1, arrow=True, fill="none", dash=None):
da = f' stroke-dasharray="{dash}"' if dash else ""
a = ' marker-end="url(#arw)"' if arrow else ""
self.parts.append(
f'<path d="{d}" fill="{fill}" stroke="{stroke}" stroke-width="{sw}"{da}{a}/>'
)
def render(self) -> str:
body = "\n ".join(self.parts)
return f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {self.w} {self.h}" \
width="{self.w}" height="{self.h}" font-family="{FONT}">
<defs>
<marker id="arw" viewBox="0 0 12 12" refX="10" refY="6" markerWidth="8" markerHeight="8"
orient="auto-start-reverse">
<path d="M 1.5 1.5 L 10 6 L 1.5 10.5" fill="none" stroke="#6f6f6f"
stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</marker>
</defs>
<rect width="{self.w}" height="{self.h}" fill="{self.bg}"/>
{body}
</svg>
"""
# --------------------------------------------------------------------------
# palette
BG = "#ffffff"
PANEL = "#eceae3"
BLUE = "#b8d4ea"
BLUE_TXT = "#123146"
CORAL = "#f0c8b4"
CORAL_TXT = "#7a3418"
GREY = "#6f6f6f"
INK = "#1a1a1a"
CAP = "#3f3f3f"
def node(c: Canvas, cx, cy, lines, fill, txt, min_w=0, pad=34, lh=38, size=27):
"""A box centred on (cx, cy), sized to its widest line."""
# measure each line at the size and weight it is actually drawn with —
# sizing every line as bold-at-full-size inflated the box and pushed it
# into its neighbour
w = max(
min_w,
max(text_width(t, size if b else size - 4, b) for t, b in lines) + pad * 2,
)
h = len(lines) * lh + 32
c.rect(cx - w / 2, cy - h / 2, w, h, fill, rx=13)
c.note_box(cx - w / 2, cy - h / 2, w, h, lines[0][0])
top = cy - h / 2 + 30
for i, (t, bold) in enumerate(lines):
c.text(cx, top + i * lh, t, size if bold else size - 4, txt, "middle", bold)
return w, h
# ============================== diagram 1 =================================
def architecture() -> Canvas:
"""Mirrors Anthropic's multi-agent research diagram: chat on the left, the
agent system on the right, lead in the middle, subagents underneath.
One thing theirs does not need: every box in their picture is a model, so
they can all be the same blue. Ours has a box where no model runs, and that
is the load-bearing claim of the design, so it gets its own colour and says
so on its face."""
c = Canvas(2000, 1125, BG)
c.text(1000, 92, "High-level architecture of Autonomous Circuit", 52, INK,
"middle", True)
# ---- left: the chat -------------------------------------------------
c.rect(30, 168, 500, 900, PANEL, rx=26)
c.text(280, 250, "The chat", 40, GREY, "middle", True)
c.rect(64, 330, 58, 58, "#c8623a", rx=14)
c.text(93, 370, "\u25c6", 28, "#ffffff", "middle", True)
c.text(146, 352, "What do you", 33, "#1f1f1f", "start", True)
c.text(146, 392, "want to build?", 33, "#1f1f1f", "start", True)
c.parts.append('<circle cx="93" cy="486" r="25" fill="none" stroke="#9a9a9a" stroke-width="2.4"/>'
'<circle cx="93" cy="477" r="9" fill="none" stroke="#9a9a9a" stroke-width="2.4"/>'
'<path d="M 79 502 a 14 12 0 0 1 28 0" fill="none" stroke="#9a9a9a" stroke-width="2.4"/>')
for i, ln in enumerate([
"a coaster that", "reminds me to", "drink water. it",
"should light up", "when my cup is", "empty. usb-c", "powered.",
]):
c.text(146, 470 + i * 40, ln, 27, "#4a4a4a")
# ---- arrows between the panels --------------------------------------
c.line(546, 430, 762, 430)
c.text(654, 404, "Your request", 26, CAP, "middle", True)
c.line(762, 700, 546, 700)
c.text(654, 674, "The files to order", 26, CAP, "middle", True)
# ---- right: the system ----------------------------------------------
c.rect(770, 168, 1200, 900, PANEL, rx=26)
c.text(1370, 250, "Multi-agent electrical engineering system", 38, GREY,
"middle", True)
LEAD_X, LEAD_Y = 1400, 500
node(c, LEAD_X, LEAD_Y, [
("circuitcode", True),
("(lead agent)", True),
("Tools: golden blocks + circuitlib", False),
("+ generator + run_subagent", False),
], BLUE, BLUE_TXT, min_w=520, lh=44, size=32)
# memory-equivalent, to the right
node(c, 1846, 500, [("Golden", True), ("blocks", True)], BLUE, BLUE_TXT,
min_w=210, size=29)
c.line(1698, 500, 1740, 500)
c.parts.append('<line x1="1740" y1="500" x2="1698" y2="500" stroke="#6f6f6f"'
' stroke-width="2.1" marker-end="url(#arw)"/>')
# the one box that is not an agent
node(c, 906, 500, [("The checks", True), ("not an agent —", False),
("plain code, no model", False)], CORAL, CORAL_TXT,
min_w=290, size=29)
c.line(1112, 470, 1094, 470) # board out to the checks
c.line(1094, 530, 1112, 530) # findings back
# subagents, packed from measured widths
subs = ["Brief subagent", "Parts subagent", "Review subagent"]
widths = [text_width(t, 29, True) + 96 for t in subs]
gap = 60
total = sum(widths) + gap * 2
x = LEAD_X - total / 2
for w, name in zip(widths, subs):
cx = x + w / 2
node(c, cx, 860, [(name, True)], BLUE, BLUE_TXT, min_w=w, size=29)
# bidirectional, like the reference
c.path(f"M {cx - 14:.0f} 806 L {cx - 14 + (LEAD_X - cx) * 0.30:.0f} 604")
c.parts.append(
f'<path d="M {cx + 14 + (LEAD_X - cx) * 0.30:.0f} 604 '
f'L {cx + 14:.0f} 806" fill="none" stroke="#6f6f6f" '
f'stroke-width="2.1" marker-end="url(#arw)"/>')
# each subagent loops on itself
c.path(f"M {cx + 34:.0f} 916 a 40 30 0 1 0 -66 8")
x += w + gap
c.text(LEAD_X, 1020, "each subagent gets one question and its own context",
26, CAP, "middle")
return c
# ============================== diagram 2 =================================
def process() -> Canvas:
"""The sequence. Height is derived from the content, never guessed."""
lanes = [
("You", "#e6f4ec", "#265f45", 130),
("The app", "#cfe9dc", "#265f45", 350),
("circuitcode\n(lead)", "#8ed2b3", "#123d2a", 610),
("The checks", CORAL, CORAL_TXT, 920),
("Review subagent", "#35a97c", "#ffffff", 1240),
("Golden blocks", "#17875e", "#ffffff", 1560),
]
X = {n: x for n, _, _, x in lanes}
HDR, STEP, SELF = 60, 66, 116
# --- pass 1: script the run, so the canvas can be sized to it ---
script: list[tuple] = []
y = 0
def m(a, b, label, bold=False):
nonlocal y
script.append(("msg", a, b, y, label, bold))
y += STEP
def sc(a, label):
nonlocal y
script.append(("self", a, y, label))
y += SELF
m("You", "The app", "describe the gadget")
m("The app", "circuitcode\n(lead)", "start a session")
m("circuitcode\n(lead)", "Golden blocks", "what could build this?")
m("Golden blocks", "circuitcode\n(lead)", "these blocks — or: nothing in the catalog fits")
m("circuitcode\n(lead)", "You", "the plan: parts, pins, rough cost")
m("You", "circuitcode\n(lead)", "you approve", True)
y += 34
loop_top = y
y += 56
sc("circuitcode\n(lead)", "write the board")
m("circuitcode\n(lead)", "The checks", "build it")
sc("The checks", "compile it, then check it two ways")
m("The checks", "circuitcode\n(lead)", "everything wrong with it")
sc("circuitcode\n(lead)", "fix what blocks it")
caption_y = y + 8
y += 54
loop_bottom = y
y += 56
m("circuitcode\n(lead)", "Review subagent", "nothing blocks it — review it")
sc("Review subagent", "seven experts, each scoring alone")
m("Review subagent", "circuitcode\n(lead)", "what to fix — round again, up to 4 times")
m("circuitcode\n(lead)", "The app", "every score 7 or better")
# --- pass 2: draw, with everything offset below the header ---
top = 150
off = top + HDR + 46
bot_y = off + y + 10
c = Canvas(1740, int(bot_y + HDR + 40), BG)
c.text(870, 66, "One run, from a sentence to a fab packet", 44, INK, "middle", True)
for _, _, _, x in lanes:
c.line(x, top + HDR, x, bot_y, stroke="#dadada", sw=2, arrow=False)
def headers(yy):
for name, fill, txt, x in lanes:
rows = name.split("\n")
w = max(text_width(r, 25, True) for r in rows) + 52
h = HDR if len(rows) == 1 else HDR + 26
c.rect(x - w / 2, yy, w, h, fill, rx=12)
for i, r in enumerate(rows):
c.text(x, yy + (38 if len(rows) == 1 else 32 + i * 28), r, 25,
txt, "middle", True)
headers(top)
headers(int(bot_y))
# the loop, drawn first so messages sit on top of it
lx0, lx1 = 548, 1680
c.rect(lx0, off + loop_top, lx1 - lx0, loop_bottom - loop_top,
"#eaf7f0", rx=18, stroke="#4cb389", sw=2.5)
lw = text_width("Until nothing is wrong", 25, True) + 56
c.rect(1114 - lw / 2, off + loop_top - 26, lw, 52, "#cfe9dc", rx=12,
stroke="#a7d9c0", sw=2)
c.text(1114, off + loop_top + 8, "Until nothing is wrong", 25, "#265f45",
"middle", True)
c.text(1114, off + caption_y, "at most 2 tries, then 3, then 2", 24,
"#2f7a58", "middle", True)
for item in script:
if item[0] == "msg":
_, a, b, yy, label, bold = item
x1, x2 = X[a], X[b]
d = 1 if x2 > x1 else -1
c.line(x1 + 6 * d, off + yy, x2 - 8 * d, off + yy)
c.text(min(x1, x2) + 18, off + yy - 16, label, 24, CAP, "start", bold)
else:
_, a, yy, label = item
x = X[a]
c.path(f"M {x} {off + yy} a 38 29 0 1 1 -6 54")
c.text(x + 56, off + yy + 34, label, 24, CAP, "start")
return c
def main() -> int:
OUT.mkdir(parents=True, exist_ok=True)
bad = 0
for name, fn in (
("multi-agent-architecture", architecture),
("multi-agent-process", process),
):
c = fn()
(OUT / f"{name}.svg").write_text(c.render())
if c.violations:
bad += 1
print(f"{name}: {len(c.violations)} element(s) outside the canvas")
for v in c.violations[:8]:
print(f" {v}")
else:
print(f"{name}: ok ({c.w}x{c.h})")
return 1 if bad else 0
if __name__ == "__main__":
raise SystemExit(main())
26e259c5cThis file is pinned to the selected release. Download the original to open it in your design tool.