karasu is a text modeling language for system architecture, inspired by the C4 model. You describe your system in .krs files and karasu draws the views: system, deployment, and team structure. I started it about six months ago to learn agentic coding, and I haven't written its code by hand. Everything goes through Claude Code.
This is the first of a weekly series. Each post covers what shipped and what building it with agents looked like.
This week in numbers: 41 PRs merged (7 of them Dependabot) and 10 decisions recorded as ADRs.
Highlight 1: a focus canvas for dense diagrams
Architecture diagrams get crowded, and edge labels are the first thing to suffer. They overlap, get truncated, or are simply unreadable.
karasu now handles this in two layers. The main canvas draws only the labels it has room for. The full text lives in a focus canvas that opens over the preview:
- Click an edge to see the two nodes it connects, with every edge between them. Each label gets its own lane and is drawn in full.
- Click the "Relations" pill on a node to see the node with all its neighbours: dependents on one side, dependencies on the other.
Later in the week the canvas became reachable without a mouse:
- On touch devices, the node detail panel offers a "Relations" button.
- From the keyboard, a command-palette entry opens the canvas for the highlighted node.
- Inside the canvas you move with Tab, and focus returns to where you came from when the canvas closes.
Highlight 2: .krs language v2.0
This was a breaking release of the language itself. The main idea is that the vocabulary is now closed.
- Tags and annotations belong to the tool. Previously you could invent your own. Now a style rule that targets a tag karasu doesn't know matches nothing, so the meaning of a diagram can't drift between projects.
-
facetandboundaryare core notation. They were experimental. Facets handle cross-cutting concerns such as "which components touch PCI data", and boundaries group nodes by membership. -
Some warnings became errors. Placing a node in a context where it doesn't belong is now an error, and so is a near-miss of a built-in annotation (for example,
@depracated). - "Error" has a definition. An error is syntax karasu does not accept, and no new diagram is drawn while one stands. The VS Code preview now keeps showing the last valid render instead of a half-recovered picture.
Smaller things worth mentioning
97% fewer SVG elements. On a large real-world model (Dify), the small arcs drawn where edges cross ("hops") made up 43% of the output. Hops are now merged into one <path> per run of consecutive hops with the same stroke. That cut hop elements from 38,572 to 894 and output size from 8.72 MB to 6.48 MB. Only consecutive hops are merged. Grouping every hop by colour would have saved slightly more, but it would have swapped the stacking order of 4,757 overlapping pairs.
Unicode names no longer lose characters. The lexer read UTF-16 units one at a time, so 𠮷野家 silently became 野家 and a decomposed café became cafe. It now reads by code point. Characters it can't place, such as a stray emoji or a zero-width space inside a name, are reported instead of dropped. Reading by code point made tokenizing a 10k-line model about twice as slow, so ASCII now takes a fast path, and the speed is back where it was.
Edge bundling in gutters. Edges running in the same gutter towards the same end now share one lane. This first made rendering about 25% slower, and a spatial index brought it back, with byte-identical output.
OGP images for the gallery. Public gallery submissions now get a social preview image. The design started as "store a PNG in KV and clean it up with a cron job". KV reads can be up to about 60 seconds stale, though, and Workers requests have no bounded duration, so a timed cleanup could never be proven correct. The final design stores nothing: the image is rendered on a cache miss and kept in the edge cache for a day. In production a miss costs 523 to 620 ms of CPU and a hit costs 2 to 5 ms.
What building with agents looked like this week
A hook that executed files. A Claude Code PostToolUse hook was meant to format TypeScript after each edit. It ran under /bin/sh (dash), which has no [[, so the condition was split at ||. The result was that the hook executed any executable file the agent had just edited, and never formatted a single .ts file. Nobody noticed for a while because nothing visibly failed. The fix was a POSIX case. The lesson: agent hooks are code too, and they deserve the same scrutiny as the code they guard.
Review moved earlier. The workflow now runs an agent code review on the branch diff before opening the draft PR. Previously it ran after. Review fixes now land in the PR's first push, so the PR history stays readable. I recorded this as an ADR that supersedes an earlier decision, because the original reason for rejecting this option no longer held.
The design doc → ADR loop. Most features this week went through the same cycle: a design doc in a PR, discussion, implementation, then promotion to an ADR, after which the design doc is deleted. Ten ADRs were recorded this way. The design docs are where the agents and I argue. The ADRs are what stays.
Coming next
Two designs were merged this week and wait for implementation: ordering channel lanes by the direction each edge turns, and making node and edge identity keys collision-free.
- Site: karasu.kompiro.dev
- Source: github.com/kompiro/karasu
Feedback and questions are welcome in the comments.

Top comments (0)