<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>HanLHo. - Fractional Architect &amp; Software Product Engineer - c4</title>
    <link rel="self" type="application/atom+xml" href="https://hanlho.com/tags/c4/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://hanlho.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-03-11T00:00:00+00:00</updated>
    <id>https://hanlho.com/tags/c4/atom.xml</id>
    <entry xml:lang="en">
        <title>Creating architecture diagrams with C4 and coding agents</title>
        <published>2026-03-11T00:00:00+00:00</published>
        <updated>2026-03-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hanlho.com/p/creating-architecture-diagrams-with-c4-and-coding-agents/"/>
        <id>https://hanlho.com/p/creating-architecture-diagrams-with-c4-and-coding-agents/</id>
        
        <content type="html" xml:base="https://hanlho.com/p/creating-architecture-diagrams-with-c4-and-coding-agents/">&lt;p&gt;LLMs can draw diagrams, but you get better results with a conceptual model, a validation loop, and a lightweight verification pass against the codebase than with free-form diagramming.&lt;&#x2F;p&gt;
&lt;p&gt;I used the &lt;a href=&quot;https:&#x2F;&#x2F;c4model.com&quot;&gt;C4 model&lt;&#x2F;a&gt; extensively to map architecture landscapes. Last week I saw an opportunity to catch up with it and try it out with coding agents. I found that modelling architecture in a text-based format with guardrails (a DSL with rules) is easier and more consistent for a coding agent. I tried it out on a small Rust project I know well. This post is a field note of my findings.&lt;&#x2F;p&gt;
&lt;aside class=&quot;sidebar-note sidebar-note-right&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;c4model.com&quot;&gt;C4&lt;&#x2F;a&gt; is a zoom-in model for software architecture.&lt;&#x2F;p&gt;
&lt;p&gt;This post discusses only the levels we actually need:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;System context: people and software systems.&lt;&#x2F;li&gt;
&lt;li&gt;Container: deployable&#x2F;runnable things inside a software system.&lt;&#x2F;li&gt;
&lt;li&gt;Component: the main building blocks inside a container.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A key element for coding agents: C4 can be expressed as a text model (a DSL), so the architecture model can be edited like code and validated&#x2F;exported via a CLI.&lt;&#x2F;p&gt;
&lt;p&gt;C4 is model-as-code: one model, many views&#x2F;diagrams.&lt;&#x2F;p&gt;

&lt;&#x2F;aside&gt;
&lt;h2 id=&quot;the-test-project&quot;&gt;The test project&lt;&#x2F;h2&gt;
&lt;p&gt;To try this out, I used one of my personal projects: a text-based &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lhohan&#x2F;simple-time-tracker&quot;&gt;time-tracking application&lt;&#x2F;a&gt; with two runtime modes (a CLI and a web dashboard). Both operate on the same domain and the same Markdown time-entry files.&lt;&#x2F;p&gt;
&lt;p&gt;The functionality does not matter much for this post, except for two things. First, the codebase is relatively small and easy to analyse. Second, it is well-structured: ports and adapters, plus behaviour-driven, DSL-based acceptance tests.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve used C4 on larger landscapes too. I expect the workflow to translate, but the experience will differ on larger (or less structured) codebases.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-the-model&quot;&gt;Building the model&lt;&#x2F;h2&gt;
&lt;p&gt;I started the coding agent session with a direct request to build C4 diagrams for the project at system and container level, with the DSL written first.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#eff1f5;color:#4f5b66;&quot;&gt;&lt;code&gt;&lt;span&gt;build me a c4 model at system level and container level (as defined by the C4 model).
&lt;&#x2F;span&gt;&lt;span&gt;Please create the DSL first
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;reference: c4model.com
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Below, I&#x27;ll go through the process using the diagrams, but keep in mind these are all generated from a text-based DSL. From the start, the agent produced a working model in the Structurizr DSL. I then gave it a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lhohan&#x2F;simple-time-tracker&#x2F;blob&#x2F;790b4799892330714977e7092682a9c9fec72499&#x2F;justfile#L109&quot;&gt;command to run Structurizr CLI&lt;&#x2F;a&gt; as a check at each step.&lt;&#x2F;p&gt;
&lt;p&gt;To start with, the agent inspected the Rust codebase to work out the system boundary. It established one &lt;code&gt;Time Tracker&lt;&#x2F;code&gt; software system with two runtime modes: a CLI and a web dashboard. Both use the same Markdown time-entry files.&lt;&#x2F;p&gt;
&lt;p&gt;(Apologies for the dark diagrams; dark mode was enabled when I took these screenshots. To enlarge them, open the images in a new tab.)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;c4-llm-system-1.png&quot; alt=&quot;C4 system context diagram (first pass)&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here is a summary of the session:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;One of the first decisions was scope: whether to model only the web path or both runtime paths. The choice was to represent them as separate containers.&lt;&#x2F;li&gt;
&lt;li&gt;We modelled a software system, two application containers, an internal datastore for run statistics, and the Markdown time-entry files as an external dependency. That first version was structurally correct.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;c4-llm-container-1.png&quot; alt=&quot;C4 container diagram (first pass)&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;(I had completely forgotten about the runtime statistics feature ...)&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;After the first version, something felt missing between the Markdown files and the CLI&#x2F;web containers: the shared core logic. In C4 terms, that isn&#x27;t another container; it belongs at component level. So I kept the container model strict and added the component level to make the shared logic explicit.&lt;&#x2F;p&gt;
&lt;p&gt;I initially asked it to model the shared core logic as a container, but the agent pushed back, and the model improved because of it. I asked it to add component views for both runtime containers instead of inventing a fake &lt;code&gt;core&lt;&#x2F;code&gt; container. That preserved a strict container model while making the architecture more insightful.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Naming discussions helped sharpen the model. The agent came up with names I was not sure about, but on a first pass it probably did a better job than I would have. One direction I set explicitly was to name things as close as possible to the codebase. The names were not bad, but this is not where I want to leave room for interpretation.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;To support those component views, we introduced a shared component fragment that both CLI and web could include. That shared layer covered parsing, domain types, reporting, and execution statistics. The result was a more accurate picture of how the code is actually organised.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;c4-llm-component-cli-1.png&quot; alt=&quot;C4 component diagram for the CLI (first pass)&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Once the model structure felt right, I shifted to presentation. I asked the agent to style it so different roles were easier to distinguish: CLI and web containers, shared components, adapters, renderers, and datastores. Then I asked for rounded boxes and a more explicit person-style user element.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The final result:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;img&#x2F;c4-llm-system-dark-1.png&quot; alt=&quot;C4 system context diagram (styled)&quot; &#x2F;&gt;
&lt;img src=&quot;&#x2F;img&#x2F;c4-llm-container-dark-1.png&quot; alt=&quot;C4 container diagram (styled)&quot; &#x2F;&gt;
&lt;img src=&quot;&#x2F;img&#x2F;c4-llm-component-cli-3.png&quot; alt=&quot;C4 component diagram for the CLI (styled)&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I have also made &lt;a href=&quot;https:&#x2F;&#x2F;lhohan.github.io&#x2F;simple-time-tracker&#x2F;site&#x2F;&quot;&gt;the generated static site with the diagrams&lt;&#x2F;a&gt; available as it was straightforward to do with help from the agent. You can click the small magnifying glass icons to zoom into the next level.&lt;&#x2F;p&gt;
&lt;p&gt;In summary, this result took several passes: boundaries first; then the component layer; then names aligned with the code; and finally presentation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-dsl-in-practice&quot;&gt;The DSL in practice&lt;&#x2F;h2&gt;
&lt;p&gt;One important artefact we have not discussed yet: the DSL itself. &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lhohan&#x2F;simple-time-tracker&#x2F;blob&#x2F;790b4799892330714977e7092682a9c9fec72499&#x2F;docs&#x2F;c4&#x2F;time-tracker.dsl&quot;&gt;Here is the full model&lt;&#x2F;a&gt; with the diagrams defined in the Structurizr DSL. All the edits were done by the agent, including the initial creation from scratch. I reviewed, asked questions, and iterated.&lt;&#x2F;p&gt;
&lt;p&gt;Before this, I typed every box and relationship by hand (scrolling up and down the file, or keeping two windows open), added tech stacks (taking care not to confuse the order of strings), and so on. Using the agent was a major documentation speed boost, and the DSL came out clean and organised the way I prefer: relationships after the element definitions, not inside them.&lt;&#x2F;p&gt;
&lt;p&gt;While I see the risk of not thinking things through, being relieved of painstaking manual element&#x2F;relationship editing, working with agent also gave me:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Iteration close to the code&lt;&#x2F;li&gt;
&lt;li&gt;Meaningful discussions on abstraction levels and naming&lt;&#x2F;li&gt;
&lt;li&gt;A knowledgeable architecture assistant at hand&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Why I think it worked: the model is defined in text, so the agent can edit it like code. C4 provides guardrails through a small number of nested abstraction levels, and the DSL keeps names, descriptions, and styles consistent across views. A CLI tool to validate the model closes the loop, so the agent can check its work as it goes.&lt;&#x2F;p&gt;
&lt;p&gt;In addition, you can ask the LLM to review the model, in the context of the actual codebase or not.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;operationalising-the-workflow&quot;&gt;Operationalising the workflow&lt;&#x2F;h2&gt;
&lt;aside class=&quot;sidebar-note sidebar-note-right&quot;&gt;
  &lt;p&gt;I used Codex CLI with Codex 5.3; any other recent coding agent and model will probably work as well.&lt;&#x2F;p&gt;

&lt;&#x2F;aside&gt;
&lt;p&gt;Going forward, here is how I will instruct LLMs to work with C4 and keep the architecture diagrams up to date.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;agent-skill&quot;&gt;Agent Skill&lt;&#x2F;h3&gt;
&lt;p&gt;First, after completing this experiment, I turned my learning into &lt;strong&gt;a reusable agent skill called &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lhohan&#x2F;agent-chisels&#x2F;blob&#x2F;c198e00546f1274e3afcdda58dfd74423fcaa29c&#x2F;agentfiles&#x2F;shared&#x2F;skills&#x2F;modelling-c4-diagrams&#x2F;SKILL.md&quot;&gt;&lt;code&gt;modelling-c4-diagrams&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;, which I can now use from any project.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;agents-md-instructions&quot;&gt;AGENTS.md instructions&lt;&#x2F;h3&gt;
&lt;p&gt;In the project&#x27;s &lt;code&gt;AGENTS.md&lt;&#x2F;code&gt; I added &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lhohan&#x2F;simple-time-tracker&#x2F;blob&#x2F;790b4799892330714977e7092682a9c9fec72499&#x2F;AGENTS.md?plain=1#L12&quot;&gt;a short reference&lt;&#x2F;a&gt; so future agents can discover the DSL files and know how to validate&#x2F;export. This avoids repeating the discovery work in each new session.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;markdown&quot; style=&quot;background-color:#eff1f5;color:#4f5b66;&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;- &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#d08770;&quot;&gt;**Architecture docs (C4)**&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;: source DSL at &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`docs&#x2F;c4&#x2F;time-tracker.dsl`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; (shared components in &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`docs&#x2F;c4&#x2F;shared-tracking-core.dsl`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;); validate with &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`just architecture-docs-validate`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;; export static site with &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`just architecture-docs-export`
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;verification&quot;&gt;Verification&lt;&#x2F;h3&gt;
&lt;p&gt;In this project, the LLM and I used the following commands to verify the output:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Validate C4 Structurizr DSL:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;structurizr-cli validate -workspace docs&#x2F;c4&#x2F;time-tracker.dsl&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Export C4 diagrams to docs&#x2F;site for inspection (and GitHub Pages publishing)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;structurizr-cli export -workspace docs&#x2F;c4&#x2F;time-tracker.dsl -format static -output docs&#x2F;site&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;View the architecture documentation
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;open docs&#x2F;site&#x2F;index.html&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;structurizr-interface-to-llm&quot;&gt;Structurizr interface to LLM&lt;&#x2F;h3&gt;
&lt;p&gt;I found the validation loop with the CLI to work well. If the export succeeds, the DSL is valid and the views conform to the tool&#x27;s rules.&lt;&#x2F;p&gt;
&lt;p&gt;That still does not tell you whether the model is accurate, or whether the diagrams communicate well. The &lt;a href=&quot;https:&#x2F;&#x2F;c4model.com&#x2F;diagrams&#x2F;checklist&quot;&gt;C4 diagram review checklist&lt;&#x2F;a&gt; is a good yardstick.&lt;&#x2F;p&gt;
&lt;p&gt;The LLM did not seem to require much extra instruction to create a proper model and views. I pointed it to c4model.com at the beginning of the session, and that may have been enough context. (Hard to tell what it knows or does under the hood.)&lt;&#x2F;p&gt;
&lt;p&gt;The skill I created and referenced above now serves as a main interface.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;The architecture model and diagrams are insightful artefacts (&quot;pictures can say more than words&quot;). But most of the thinking and modelling usually happens visually, while recording it often becomes a chore. This experiment showed me that LLMs can help keep a model up to date without turning it into a separate manual process.&lt;&#x2F;p&gt;
&lt;p&gt;When the model is constrained (C4) and expressed as text (a DSL), you can version it like code, review it like code, and validate&#x2F;export it through a CLI. Constrained text models plus validation give coding agents a better architecture-diagram workflow than free-form diagramming.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;addendum-next-experiments&quot;&gt;Addendum: Next experiments&lt;&#x2F;h2&gt;
&lt;p&gt;Some follow-ups I might try if I run this workflow again.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;keeping-the-model-in-sync&quot;&gt;Keeping the model in sync&lt;&#x2F;h3&gt;
&lt;p&gt;Work with the LLM to design how to encode parts of the architecture model directly in the codebase. Use the C4 views as shared context, then define a way to keep the model in sync with the implementation. Unless you are using a very principled framework (maybe Spring in Java?), I expect this to be quite custom per project anyway.&lt;&#x2F;p&gt;
&lt;p&gt;Coding agents may lower the barrier to getting started with this kind of non-obvious quality-improvement work.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;verification-beyond-the-cli&quot;&gt;Verification beyond the CLI&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use an MCP like Chrome DevTools to inspect exported diagrams as a second verification step.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;One concrete use case: manual editing is often required to position boxes and, especially, dependencies. A visual inspection could double-check that no text boxes overlap and that lines do not cross boxes.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Coding agents could be used to evaluate the shape of the architecture outside of the code.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;publishing-and-representation&quot;&gt;Publishing and representation&lt;&#x2F;h3&gt;
&lt;p&gt;Export to Mermaid (or PlantUML) for embedding in the agent&#x27;s instructions, but keep the Structurizr DSL as the source of truth. Split the DSL so documentation for each container or component lives closer to the code.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;a-better-llm-interface-in-tooling&quot;&gt;A better LLM interface in tooling&lt;&#x2F;h3&gt;
&lt;p&gt;This requires changes to Structurizr. It could provide build&#x2F;run instructions for LLMs via an extensive &lt;code&gt;--help&lt;&#x2F;code&gt; output, or ship &lt;a href=&quot;&#x2F;p&#x2F;add-a-cli-subcommand-to-keep-llm-instructions-in-sync&#x2F;&quot;&gt;a dedicated subcommand that prints LLM instructions&lt;&#x2F;a&gt; (similar to &lt;code&gt;bd prime&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
