A notation for designing agent systems: before you build them.
There is no C4 for agentic systems. Most agent diagrams are boxes and arrows that describe a happy path and say nothing about the parts that actually determine whether the system is safe: how it can exit, what is enforced versus merely instructed, and which decisions are fixed at authoring time versus left to the model at runtime.
This notation exists to make those visible. Thirteen element kinds, eleven exit types, eight hook points, and a set of analyses that read the design rather than the prose around it.
When to use these
Designing a multi-agent or tool-using system before writing the orchestration.
Reviewing an agent design for the ways it can end that nobody modelled.
Making the difference between a requirement and a control legible, since one is instruction and the other is enforcement.
When not to
Documenting a single prompt with no tools. That is a paragraph, not a diagram.
Runtime tracing. This is a design notation, not an observability tool.
Common mistakes
The commonest error in hand-made agent diagrams is drawing a guardrail as a flow node. A control attaches *beside* a stage on the hook plane; it is never a step in the sequence. Drawing it inline implies an ordering that does not exist.
The second is treating a requirement as if it were enforced. A requirement is met by instruction (no tool call, no state write, no branch), which means it is the part of your design with no assurance behind it at all. The notation marks that distinction on purpose.
L1: the whole estate. Which agents exist and who talks to whom.
Show the source
@startuml
!include <agentic/core>
title Refund assistant — L1 Landscape
' VIEWPOINT: Landscape — who does this serve, what does it touch, and where
' does control leave our estate? Start here. Almost nobody does, which is why
' boundary questions surface late.
' DONE WHEN: every actor and every channel is on it, and each one sits inside
' a named zone.
'
' Every element is Kind(alias, "Label", "description"). The description is
' optional and carries what the kind's meta line carried in v1.
Actor(customer, "Customer", "verified at the support desk")
Channel(voice, "Inbound call", "voice · 800ms latency budget")
' Any element opens a composite by putting a brace at the call site.
Zone(cc, "Support desk") {
Agent(assistant, "Refund assistant", "opus-5 · effort high")
}
Zone(estate, "Order estate") {
Tool(policy, "Payments system", "IssueRefund v1.0.0 · writes to system of record")
L2: a router topology, where one agent dispatches to specialists.
Show the source
@startuml
!include <agentic/core>
title Support assistant — L2 Topology
' VIEWPOINT: Topology — what shape is this, and who routes? The orchestration
' spectrum runs scripted flow -> router -> supervisor -> swarm -> solo. This is
' a ROUTER: one classifier picks a specialist on a guard the author fixed.
' DONE WHEN: the position on the spectrum is declared and every route is typed.
'
' Rel_Expr is a deterministic guard, Rel_LLM is the model's judgement, and a
' bare Rel is an unguarded route.
Actor(customer, "Customer")
Channel(inbound, "Inbound request", "chat · carries intent, sensitivity")
Agent(router, "Router", "classifier · haiku-4-5 · effort low")
Agent(billing, "Billing specialist", "opus-5 · reads the ledger")
Agent(shipping, "Shipping specialist", "opus-5 · reads carrier tracking")
Exit_Human(human, "Escalate")
Exit_Complete(done, "Answered")
Rel(customer, inbound)
L2: a peer mesh with handoffs and no central coordinator.
Show the source
@startuml
!include <agentic/core>
title Incident response — swarm topology
' VIEWPOINT: Topology, choreographed end. There is NO author-defined path here:
' peers decide at runtime who takes over. Read every Handoff as "MAY hand to",
' never "then" — the renderer lays this out with a force algorithm precisely so
' the y-axis cannot be mistaken for a sequence.
' DONE WHEN: no edge in the mesh implies an order the runtime does not enforce.
'
' Delegate hands work down and EXPECTS IT BACK; Handoff means a peer takes over
' and does not return. A swarm is made of the second kind.
Actor(monitoring, "Monitoring", "external system")
Channel(alert, "Alert", "event · carries service, severity")
Agent(triage, "Triage", "entry peer · opus-5")
Agent(logs, "Log analyst") {
Tool(t_query, "Query telemetry")
}
Agent(mitigate, "Mitigator") {
Tool(t_rollback, "Rollback release")
}
L2: what each agent may call, read and load. No sequence.
Show the source
@startuml
!include <agentic/core>
title Research analyst — capability matrix
' VIEWPOINT: Capability matrix — which agent has which tools, knowledge and
' model tier? Nest them: braces are containment, so everything inside the agent
' below IS the agent's surface. With no edges among them there is no sequence,
' so a solo agent with ten unordered tools draws as one node with capability
' strips rather than a fake pipeline.
'
' There is no attachment macro. Braces are the only thing that means
' containment, so a capability two agents SHARE is declared in each of them —
' which is honest about the fact that each carries its own copy of that
' contract.
' A skill is the third kind, and the conditional one:
' Skill_Context instructions only, folds into the agent as a chip
' Skill_Actions has a procedure — modelled with steps, like a sub-agent
' Unlike a sub-agent it runs in the CALLING agent's context, so it costs that
' agent's window rather than opening its own.
' Say where each tool LIVES, because it decides who can change it:
' Tool external — someone else's contract, versioned with them
' CodeTool a function in this repo, shipped and changed with the agent
' There is no binding to declare: a tool in the flow is called on the way
L3: one journey end to end, including how it exits.
Show the source
@startuml
!include <agentic/core>
title Order refund — L3 Flow
' VIEWPOINT: Flow — what happens step by step, and where does it branch?
'
' A SubAgent is an AGENT, not a boundary drawn round some steps. It has its own
' model, its own tools and its own hooks, exactly like a leaf agent — the only
' difference is that it also contains the steps it runs internally.
'
' So the WORKFLOW LINKS AGENT TO AGENT: Rel(entry, orchestrator), not
' Rel(entry, welcome). Reaching into a sub-agent to wire up one of its steps
' says the caller knows how that agent is built, which is exactly the coupling
' a sub-agent exists to prevent. Internal steps connect to each other; exits may
' leave from inside, because giving up is a fact about the step that gave up.
'
' Rel_Expr is a deterministic guard, Rel_LLM is the model's judgement.
'
' Whether reaching a tool RUNS the call is not something you type — it follows
' from where the tool sits. A tool in the flow is reached, so the call happens;
' a tool folded into a capability strip has no position, so nothing guarantees
' it. The renderer reads that off the diagram.
' DONE WHEN: every branch carries a typed guard, and no edge reaches past an
L4, the hook plane on its own: what runs before and after each turn.
Show the source
@startuml
!include <agentic/core>
title Refund assistant — control plane
' VIEWPOINT: Control plane — where do guardrails sit, what do they do, and what
' do they cost? Nest a Control in the agent it wraps; it is lifted OUT of the
' flow, because a guardrail wraps every turn rather than being a step in one.
' Drawing guardrails as flow nodes is the commonest error in hand-made agent
' diagrams. The description carries the hook point and the mode:
' before-invocation before-model stream-delta after-model
' before-tool after-tool on-message after-invocation
' Mode is blocking (costs latency on the critical path), streaming, or shadow.
'
' Requirement is the same obligation one level down: instructed, with no
' mechanism behind it. The renderer lists them as "prompted, not enforced".
' Promote the ones that matter to a Control or a Rel_Expr guard.
' DONE WHEN: every hook point is either covered or explicitly waived, and every
' requirement is either promoted or deliberately left as instruction.
Actor(customer, "Customer")
Channel(inbound, "Inbound", "chat · carries customer_id, question")
Agent(assistant, "Assistant", "opus-5 · effort high") {
Where untrusted input crosses into privileged capability.
Show the source
@startuml
!include <agentic/core>
title Refund assistant — trust boundary
' VIEWPOINT: Trust boundary — what crosses which boundary: credentials, PII,
' execution? This is STRIDE's move applied to agents, and it is the cell a
' security reviewer asks for first. Mark anything you have not verified with
' $tags="Unconfirmed"; the renderer flags it on the canvas and in the ledger, so
' an assumption cannot quietly harden into fact.
' DONE WHEN: every crossing is named, and every crossing is justified.
Actor(customer, "Customer")
Channel(voice, "Inbound call", "voice · recorded")
Zone(ours, "Our estate") {
Agent(assistant, "Assistant", "opus-5 · runs on our infra")
State(session, "Session state", "session scope · holds order_id")
}
Zone(estate, "Order estate") {
Tool(cancel, "Issue refund", "carries order_id · justified")
}
Every way the system can stop. Usually the most revealing one to draw.
Show the source
@startuml
!include <agentic/core>
title Support assistant — exit map
' VIEWPOINT: Exit map — every way out, and where each one goes. Exits are the
' metric surface: containment is computed from them, and it is the number the
' business asks about. All eleven types exist in the notation so the renderer
' can list the ones you did NOT model — a drawing has no opinion about what
' isn't on it. One macro each, so the set is discoverable by typing "Exit_":
' Exit_Complete Exit_AgentTransfer Exit_Human Exit_Guardrail
' Exit_Refusal Exit_Budget Exit_Loop Exit_Context
' Exit_Interrupt Exit_Retries Exit_Timeout
' A bare Exit() is one whose type has not been decided yet, and the renderer
' says so rather than guessing.
' DONE WHEN: all eleven have been considered, and the omissions are deliberate.
Actor(customer, "Customer")
Channel(inbound, "Inbound", "chat · carries question")
Agent(assistant, "Assistant", "opus-5 · max 8 cycles")
Exit_Complete(done, "Answered")
Exit_Human(human, "Escalate to a human")
Every template here ships in Gnomon and renders on your machine, with no account and nothing sent to a server. The browser editor is free and needs no install.