The diagram is the code.
An architecture model for solution architects that lives in your repository as C# source: edit the canvas, and Roslyn rewrites the file. Then walk stakeholders through it in a narrated book that travels with the model and cannot go stale either.
- Storage
- Your Git working treeNo runtime database, at any layer
- Engine
- Roslyn DocumentEditorTargeted rewrites, never string rebuilds
- Guarantee
- Round-trip fidelityA UI edit diffs as only that edit
- Narrative
- Story booksThe walkthrough that lands it, kept with the model
Your model is a file you can read.
No project format, no XML, no notation to learn. A model is C# records in Architecture/Architecture.cs, and anyone on the team can open it in a text editor and follow it.
// Bounded contextsvar ctxOrdering = new BoundedContext("ctx_ordering", "Ordering");var ctxPayments = new BoundedContext("ctx_payments", "Payments");var ctxFulfilment = new BoundedContext("ctx_fulfilment", "Fulfilment"); // Modules, each declaring the context it belongs tovar modCart = new Module("mod_cart", "Cart", "ctx_ordering");var modOrder = new Module("mod_order", "Order Lifecycle", "ctx_ordering");var modAuth = new Module("mod_auth", "Authorisation", "ctx_payments");var modLedger = new Module("mod_ledger", "Ledger", "ctx_payments"); // A relationship carries what moves, not just that// something movesvar flowOrdered = new DataFlow("flow_ordered", "mod_order", "mod_auth", "OrderPlaced");var depSettleAuth = new Dependency("dep_settle_auth", "mod_settlement", "mod_auth", "uses"); // Where a thing is in its life, and who answers for itvar tagLedger = Tag.For(modLedger, lifecycle: new Lifecycle(Status: "to-be-created", Phase: "Q4"), ownership: new Ownership(Squad: "Money", Domain: "Payments")); // The parts of a design that have no boxvar riskDualWrite = new Risk("risk_dualwrite", "Order and Ledger are written in two transactions", "mod_ledger");var qRefund = new Question("q_refund", "Who owns a partial refund after delivery?", "mod_ledger");
Contexts are declared, not drawn.
A bounded context is a record with an id and a name. On the canvas it renders as a lane that other elements nest inside — but the nesting is a real reference in the file, not a position on a grid. Move a module between lanes and one string argument changes.
Ids are stable; names are not.
Every element carries an id that never changes and a display name that can. Flows, dependencies, tags and views all reference the id, so renaming a concept as your understanding improves costs nothing and breaks nothing.
An arrow that says what it carries.
A DataFlow names its payload and its direction; a Dependency names its kind — uses, calls, reads, publishes, consumes. A link renders in a view only when both of its endpoints are present, so a filtered view never shows an arrow into nothing.
Target state lives on the model, not in a deck.
Lifecycle marks a thing current, target, to-adapt, to-be-created or deprecated, with a phase and validity dates. Ownership attaches a squad, a domain and RAPID roles. Both go on elements and on relationships, which is where migration plans usually fall apart.
Risks and questions are first-class.
A risk, an open question or a standing assumption is a model element anchored to what it concerns. It renders as a dotted edge to its subject, versions with it, and turns up on the Concerns board — instead of in a wiki page nobody opens.
One rename. Three lines. Nothing else moved.
Double-click the Ledger box on the canvas, type a longer name, press Enter. This is what git diff reported — all three hunks, with the file header removed and two over-long lines cut at the right margin.
@@ -39,7 +39,7 @@ public static class Architecture
var modAuth = new Module("mod_auth", "Authorisation", "ctx_payments");
var modSettlement = new Module("mod_settlement", "Settlement", "ctx_payments");
- var modLedger = new Module("mod_ledger", "Ledger", "ctx_payments");
+ var modLedgerReconciliation = new Module("mod_ledger", "Ledger & Reconciliation", "ctx_payments");
var modAllocation = new Module("mod_allocation", "Stock Allocation", "ctx_fulfilment");
var modShipping = new Module("mod_shipping", "Shipping", "ctx_fulfilment");
@@ -86,7 +86,7 @@ public static class Architecture
var tagSettlement = Tag.For(modSettlement, lifecycle: new Lifecycle(Status: "target", …
- var tagLedger = Tag.For(modLedger, lifecycle: new Lifecycle(Status: "to-be-created", …
+ var tagLedger = Tag.For(modLedgerReconciliation, lifecycle: new Lifecycle(Status: "to-be-created", …
var tagAllocation = Tag.For(modAllocation, lifecycle: new Lifecycle(Status: "current"), …
@@ -98,7 +98,7 @@ public static class Architecture
ctxOrdering, ctxPayments, ctxFulfilment,
modCart, modPricing, modOrder,
- modAuth, modSettlement, modLedger,
+ modAuth, modSettlement, modLedgerReconciliation,
modAllocation, modShipping,
- The declarationThe display name changed. The id did not, so every reference elsewhere still resolves.
- The local variable, twiceRoslyn renamed the symbol, so the tag and the aggregate list followed it. A find-and-replace would have missed one and still compiled.
- And nothing elseNo reordering, no reformatting, no blank line moved, no comment lost. The reviewer sees a rename, because that is what happened.
This is the contract the rest of the tool rests on. A suite of minimal, realistic and deliberately pathological fixtures runs every operation and asserts the diff byte for byte, and the codebase has one standing instruction about it: when the fidelity suite fails, the implementation is wrong, not the test.
Everything you are allowed to say.
Deliberately richer than a class diagram, deliberately poorer than an IDE. This is the whole language — there is no hidden second half.
Elements
| Record | Carries |
|---|---|
| Person | An actor, with a role — external, internal, user |
| SoftwareSystem | A system, usually a neighbouring one you don't own |
| Container | A deployable unit inside a system, with a kind — service, worker, cronjob, db |
| BoundedContext | A DDD context; renders as a lane other elements nest inside |
| Module | A unit of cohesion, optionally inside a context |
| Capability | A business capability, optionally inside a context |
| UseCase | A user-visible use case |
| Risk | A design risk, anchored to what it concerns |
| Question | An open design question, anchored the same way |
| Assumption | A standing assumption everything downstream rests on |
Relationships, metadata, views
| Record | Carries |
|---|---|
| DataFlow | From, to, a payload name shown as the edge label, and a direction |
| Dependency | From, to, and a kind — uses, calls, reads, publishes, consumes |
| Lifecycle | Status (current, target, to-adapt, to-be-created, deprecated, or your own word), phase, valid-from, valid-until |
| Ownership | Squad, domain, and RAPID role lists — recommend, agree, perform, input, decide |
| Tag.For | Attaches a lifecycle and/or an ownership to any element or any link |
| View | A named subset with a base lens — module map, dependency graph, or all — stored as its own .cs file |
One model. Whatever lens the room needs.
A view is a projection, not a copy — a named subset of the same model with its own layout, styling and notes. Switching one changes the lens, never the data.
An architecture nobody followed is an architecture that didn't happen.
Modelling the system is half the job. The other half is forty minutes in front of people who control the budget — and that walkthrough is normally a slide deck, rebuilt by hand every quarter, drifting from the model the moment either one moves.
books: - id: book_steering name: Steering review — extracting Payments audience: leadership pages: - viewId: moduleMap title: Where the money moves today narrative: Three contexts. Ordering and Fulfilment are already out of the monolith. Payments is the one still half in — Settlement is target state for Q3, Ledger does not exist yet… - viewId: dependencyGraph title: The dependency that sets the order of work narrative: Order Lifecycle still reads the legacy monolith. That arrow is why Payments cannot go first…
A view, plus the thing you would actually say.
Each page points at a view of the model and carries a title and a narrative. Not a caption — the sentence you say out loud when that picture is on the screen, written down so it survives you being on holiday.
One model, one deck per room.
A book is tagged for who it is for — leadership, engineering, security, operations. The committee that funds the work and the team that builds it get different sequences, different depth and different language, off the same model, with no second copy to keep honest.
The canvas follows the story.
Open a book and the tool becomes the deck: the canvas switches to each page's view, the narrative sits in a strip beneath it, and prev/next walks the room through it. Drag to reorder pages, edit the narrative in place, export the whole thing as a multi-page PDF for the people who want a document.
The deck cannot drift either.
A book stores the reference to a view, never a picture of it. Change the architecture and next quarter's walkthrough redraws itself from the current model — so the story you told the board and the code you shipped stay the same artifact. That is the part no diagramming tool can offer, because it has no model to point at.
Four layers, one direction, no second store.
The engine is pure Roslyn. It knows nothing about the web layer and nothing about any model provider, which is what keeps the fidelity guarantee testable in isolation.
Not a better canvas.
Three honest categories of tool already exist, and each is good at what it set out to do. This is what changes when the model has nowhere to drift to.
| Stemma | Canvas drawing tools | Model-as-language tools | Code analysers | |
|---|---|---|---|---|
| Source of truth | Your source files | A separate document | A separate language beside the code | Your source files, read-only |
| Redesign in the picture | Yes — the code follows | Yes, and nothing follows | Yes, and the code still doesn't follow | No |
| Drift | Structurally impossible | Immediate and permanent | Slower, but there are still two artifacts | None, at the price of not designing |
| Reviewed in a pull request | As a diff of the architecture | As an image somebody re-exported | As a diff of the description | Not applicable |
| Risk, ownership, questions | First-class, anchored, versioned | A sticky note, if you're lucky | Limited | Absent by construction |
| Telling the story | Narrated books that travel with the model | A slide deck, rebuilt by hand each time | Not addressed | Not addressed |
| Where a coding agent meets it | In the repository it is already editing | Nowhere near it | A file beside the code, easy to ignore | After the fact, as a report |
For a team the practical differences are narrow and concrete: an architecture review stops opening with twenty minutes of establishing whether the picture is current; a module reaching across a context boundary shows up in the diff a reviewer already reads; and a new architect has one file and one canvas to read instead of a wiki space to excavate. There is nothing to procure — it runs on a laptop, against a repository you already own, and nothing leaves it.
Technical specifications.
- Engine
- .NET 10 · Roslyn. Loads a repository through
MSBuildWorkspace, or a model-only folder through anAdhocWorkspace. All edits areDocumentEditorrewrites. - Host
- ASP.NET Core 10 — REST for workspace, layout, views and violations; SignalR for the operation stream and delta broadcast.
- Client
- React 19 · Vite ·
@xyflow/react· zustand · Tailwind. - Requirements
- A .NET 10 runtime. A model-only workspace opens with no SDK installed, no restore and no NuGet — the metadata references come from the assemblies the process is already running on.
- Model storage
Architecture/*.csandViews/*.csin your Git working tree.- Presentation
stemma.layout.json, committed beside the model: positions, node and edge styling, routing, notes, custom properties, free-form shapes, per-view layout choice.- Runtime database
- None. The model lives in memory while a workspace is open; permanent storage is the files.
- Operations
- Add, rename and remove elements; re-parent; set attributes; add and remove links; set link attributes; set lifecycle; set ownership; restore — each with its own round-trip fixtures.
- Views
- Module map, dependency graph and concerns board built in; any number of saved custom views, each stored as code.
- Layout
- Architectural (by type), hierarchical, force-directed and manual — tunable per view, remembered per view. A manual move flips that view to custom.
- Canvas
- Pan, zoom (0.05×–2.5×), minimap, snap to 20px grid, multi-select, align and distribute, undo and redo, six connection dots per node, draw.io-style edge docking, four routing modes, both-end markers.
- Export
- PNG · SVG · draw.io · Mermaid · multi-page PDF view-books · a single-file interactive HTML report that opens offline with no network requests.
- Report
- Audience modes (builder, stakeholder, reviewer), per-view layer toggles, element search, and element-anchored comments that export as a pack and import back into the workspace.
- Story books
- Ordered, audience-tagged sequences of pages; each page references a view and carries a title and a narrative. Present mode drives the canvas from the page, with prev/next, drag-to-reorder, in-place narrative editing and multi-page PDF export. Stored as
Concepts/view-book.stemma.yamlbeside the model. - Validation
- Dangling references and architecture-rule violations surfaced inline on the canvas and in the status bar.
- Collaboration
- Git. Branch, tag, blame, merge, revert, pull request. Live sync over SignalR for clients open on the same workspace; external file edits reconcile without clobbering in-flight work.
- Fidelity
- Every operation carries minimal, realistic and pathological round-trip fixtures. A failing fixture blocks the change, not the other way round.
- Licence
- Source-available. The exact terms are still an open decision and will be settled in writing before the code lands, not after.
Horizons, not dates.
A side project built on evenings and weekends since 4 May 2026. Promising quarters would be a lie with a nice font, so each item carries an honest position instead. The words are Stemma's own lifecycle vocabulary — the same three it puts on your systems.
Now current
- The Roslyn engine and the fidelity suite that gates it
- The canonical model — elements, links, lifecycle, ownership, views as code
- The canvas — module map, dependency graph, saved views, layout, styling, shapes, notes
- The committed sidecar that keeps presentation out of the model
- Story books — narrated, audience-tagged page sequences, with present mode and PDF export
- The single-file HTML report, with audience modes and a comment loop
- Model-only workspaces and the from-scratch onboarding path
Next target
- Fidelity fixtures across the whole operation catalogue — every operation, no exception
- A desktop window, so it stops reading as a dev server
- One-command install as a global tool
- Clearer view management — faster switching, better handling
- An accessibility pass — keyboard, focus, contrast, reduced motion
- A curated sample gallery as living documentation
Later to‑be‑created
- More projections — Mermaid and PlantUML output, a live code-preview pane
- Git-backed sessions — a session is a branch, a save is a commit
- Deeper validation — background compilation, richer rules inline
- Canvas search and filter, and deep-linkable views
- A repository integration that regenerates the report on merge and comments the architecture change on the pull request
- Hosted report links — the one part that could ever be a service
What people ask first.
Six objections, answered where you can find them rather than three emails in.
Does the diagram stay in sync with the code?
It cannot drift, because it is the same artefact. The model is C# in Architecture/*.cs inside your repository, and a canvas edit is a Roslyn DocumentEditor rewrite of that file. There is no export step to forget and no second copy to reconcile.
Does my whole system have to be written in C#?
No. C# is the notation the model is written in, not a constraint on what you model. The workspace can be a model-only folder with no solution in it, and the systems it describes can be written in anything. A .NET runtime is the only requirement, and a model-only workspace opens with no SDK, no restore and no NuGet.
Where is the data stored?
In your Git working tree, and nowhere else. There is no runtime database at any layer. The model is C# source; presentation — positions, styling, routing, notes — is a stemma.layout.json sidecar committed beside it. While a workspace is open the model lives in memory; permanent storage is the files.
How is this different from draw.io, Lucidchart or an EA tool?
Drawing tools produce a picture that is true on the day it is saved. Reverse-engineering tools read code but cannot edit it. Stemma closes the loop in both directions and guarantees round-trip fidelity, so a UI edit arrives as a diff containing only that edit — which is what makes the loop trustworthy enough to use.
What does a change look like in review?
A normal Git diff. Renaming one element on the canvas rewrites the declaration and every reference Roslyn can see, and touches nothing else: no reordering, no reformatting, no lost comment. Reviewers read it as the change it was.
How do stakeholders see the model without installing anything?
Two ways. A story book is an ordered, audience-tagged sequence of pages that drives the canvas as you present, stored beside the model so it cannot go stale either. Or export a single self-contained HTML report — one file, opens offline with no server, no login and no seat, with audience modes and element-anchored comments that import back.
The argument behind all of it is an essay: The Diagram That Cannot Lie — why a diagram that can drift always does. On what diagrams are actually for, there is Diagrams Are Conversations, Not Documentation.
The repository.
The repository is open. The engine lands in it once the operation catalogue is fully covered by fidelity fixtures — the guarantee has to hold everywhere before it is offered to anyone, and that is the one thing worth waiting for. Watch it there to know when.
If you want to argue with any of the above — which is more useful to me than agreement — write to me. Architects who have lived with the drift problem long enough to be cynical about it are exactly the readers I want.
Reading this with a machine? The whole page is also plain markdown, and the site's context file is llms.txt.
Stemma is a pet project. It is built by one person on evenings and weekends, it is open source, and it is not a company — there is no roadmap I owe anybody, no support line and no sales call at the end of this page. It exists because I wanted the tool and nobody had built it.