Diagrams are views
Every tool that draws software draws one layer. C4 tools give zoomable architecture, and the bottom of the zoom is a link to a repository. BPMN modelers draw executable processes, disconnected from the screens that start them and the data they change. IFML models user interaction, and its metamodel has hooks for external process and data models that nobody wired up. SHACL describes data in a silo of its own.
SemanticSeams starts from the other end. One RDF graph is the single source of truth, and every diagram is a view over it. Each element of each layer, whether a C4 container, an IFML view component, a BPMN task, a DMN decision or a SHACL shape, is a resource with an IRI. The relationships between layers are triples like any other.
Zooming in changes the notation
Because everything is one graph, zooming in is a traversal, not a feature of a drawing tool. Clicking a node follows an edge, and the viewer mounts whichever renderer the target model's notation calls for. The canonical descent crosses four notations:
- The C4 architecture view, the navigation shell. Click a container.
- Its IFML surface: pages, components, events, flows. Click an event.
- The BPMN process behind it. Click a business-rule task or a data object.
- The DMN decision table, or the SHACL shape of the data.
No other tool switches notation at the bottom of the zoom. That is the project's novel part, and Using the viewer shows it working.
Layers and their notations
| Layer | Notation | How it is held |
|---|---|---|
| Architecture | C4-style containers and deployment | The arch: vocabulary |
| Interaction | IFML | The ifml: vocabulary, documented against OMG IFML |
| Process | BPMN 2.0 | Native XML, rendered by bpmn-js; only seam-participating elements are lifted, typed by proc: |
| Decision | DMN | Native XML, rendered by dmn-js |
| Lifecycle | Harel statecharts | The state: vocabulary |
| Data | RDF and SHACL | SHACL shapes are the vocabulary |
| Concepts | OWL classes, anchored in gist | A seam:Ontology model graph that imports a vendored, pinned gist version (ADR-0022); drawn as a class view |
| Communication | Operations and channels | Hydra, plus comm: for channels |
| Deployment | Artifacts, configuration, placement | The deploy: vocabulary |
There is deliberately no BPMN or DMN ontology. Those standards already have excellent XML formats and renderers, so the XML stays the source and the graph indexes only the elements that take part in a cross-layer edge.
The data and concept layers are two views of one thing. A shape is a contract for one form, list or request; an ontology class says what the thing is, whichever screen shows it, so one class can have many shapes. A model needs no ontology to generate, and gist, the upper ontology its classes extend, is read to check the import and label the class view but is never added to the model. The expense tutorial's chapter 9 declares Expense, Payment, Approval, ExpenseState and ApprovalOutcome this way.
A shape joins the two with sh:targetClass: ExpenseShape describes an Expense (ADR-0023). Its fields name themselves with sh:name, so the RDF property can change while a form field and a payload member keep their names. A field whose values are a category's members (sh:class :ExpenseState) gets its options from the model, the lifecycle's states in lifecycle order, instead of a copied list of strings. Forms and payloads still carry each member's label, so chapter 9's generated app is the same as before it targeted the classes.
Derived sequence diagrams
A sequence diagram is never authored. seams sequence <event> derives one from the graph: the actor from the event's governing policy, the lifelines from the containers that present, expose, store and consume, and the step order from the BPMN sequence flows. There is one diagram per path through the process's exclusive gateways rather than one diagram with alt blocks. An arrow the deriver cannot place is written into the diagram as a %% gap: line, so the diagram doubles as an audit of the model: a gap names the fact that is missing. See the seams CLI. The chat app's send-message event derives to the diagram below, embedded from the golden file the generator tests pin, docs/generation/sequences/chat-app/send_message.mmd. The viewer shows the same diagrams per event under Sequences.
sequenceDiagram
%% derived from https://example.org/chat/send_message
%% path: single
actor authenticated_user as Authenticated users
participant chat_ui as Chat Web Interface
participant chat_api as Chat API
participant message_db as Message Store
participant llm_gateway as LLM Provider Gateway
authenticated_user->>chat_ui: send_message (policy: user_send_policy)
chat_ui->>chat_api: POST send_message_op [MessageShape]
Note over chat_api: start send_message_process
chat_api->>message_db: persist_user_msg writes MessageShape
Note over chat_api: t_send
chat_api-->>chat_ui: message_thread fragment
chat_api->>chat_api: route_to_provider (DMN routing_decision)
Note over chat_api: t_stream
chat_api->>llm_gateway: invoke_llm, completion_op [MessageShape]
llm_gateway-->>chat_api: completion_op returns [MessageShape]
chat_api-->>chat_ui: stream_events (token deltas)
chat_api->>message_db: persist_response writes MessageShape
Note over chat_api: t_finish
chat_api-->>chat_ui: stream_events (stream.events event)
As executed
The diagram above is as designed: what the model says a send-message does. The same lifelines also draw as executed, what one run actually did. The runtime's worker records a trace row per BPMN element it runs, and executed in seams_gen.sequence turns those rows into a diagram: the decision with the value it returned, the LLM call with the number of deltas it streamed, each transition with the status it reached, and, for a user task, the identity that completed it as an actor. The two views share the actor, UI and API lifelines, so they can be read side by side. A row the graph cannot place, such as an element the process does not have or a channel the model does not declare, is a %% gap: line here too, so a run that drifted from the model shows where. Below is a run of the chat app's send-message process, from the golden docs/generation/sequences/chat-app/send_message.executed.mmd.
sequenceDiagram
%% executed from https://example.org/chat/send_message
%% path: start, persist_user_msg, route_to_provider, invoke_llm, persist_response, stream_complete_event, end
%% run: run-1
actor authenticated_user as Authenticated users
participant chat_ui as Chat Web Interface
participant chat_api as Chat API
participant message_db as Message Store
participant llm_gateway as LLM Provider Gateway
authenticated_user->>chat_ui: send_message (policy: user_send_policy)
chat_ui->>chat_api: POST send_message_op [MessageShape]
Note over chat_api: start send_message_process
chat_api->>chat_api: persist_user_msg, send_message_op (in process)
chat_api->>message_db: persist_user_msg writes MessageShape
Note over chat_api: t_send → sending
chat_api->>chat_api: route_to_provider (DMN routing_decision) = default-provider
chat_api->>llm_gateway: invoke_llm, completion_op /v1/chat/completions
llm_gateway-->>chat_api: 3 deltas
chat_api-->>chat_ui: stream_events (3 token deltas)
Note over chat_api: t_stream → streaming
chat_api->>message_db: persist_response writes MessageShape
Note over chat_api: t_finish → complete
chat_api-->>chat_ui: stream_events (stream.events event)
To draw a run of your own, give seams sequence <event> a trace JSON file or an instance id from the runtime's SQLite store with --trace RUN (see the seams CLI). The runtime can also emit OpenTelemetry spans that carry the same element IRIs, one per instance and one per element it runs. They are off unless OTEL_EXPORTER_OTLP_ENDPOINT is set and the seams_runtime otel extra is installed (see seams_runtime/README.md). The diagram reads the trace rows, not the spans.
Derived event storm
An event storm is never authored either, and it adds no vocabulary. seams storm <process> (or the event that triggers it) reads one process's timeline from facts the model already holds, through seams_gen.storm. Each step is a trigger or a BPMN flow node, in BPMN step order; a node with none of the facts below is not a step.
| Sticky note | Derived from |
|---|---|
| Actor | The node's bpmn:Lane, and the odrl:assignee of each policy that seam:governedBy the event, the task or the operation |
| Command | Each ifml:Event that seam:triggers the process, with the operation it seam:submitsTo; a user task's seam:completedOn form and what its events submit to; a message start event that seam:subscribesTo a channel |
| Aggregate | Each shape the node seam:writes, with its sh:targetClass, seam:storedIn and seam:lifecycle |
| Domain event | The transition the node seam:fires: its state:from and state:to states |
| Decision | The DMN decision the node is seam:decidedBy |
| Channel | Each channel the node seam:publishesTo |
| Process started | Each process whose message start event seam:subscribesTo a published channel |
| Read model | Each IFML page or component that seam:reads an aggregate, with the GET operation that seam:rendersComponent it |
A fact the view expects and cannot find is a gap, listed under the timeline and printed to stderr, never dropped: a trigger or user task with no actor, a user task with no form, an aggregate nothing reads, a channel nothing consumes or subscribes to, a process nothing starts. Chapter 10's approval process derives to the timeline pinned by tests/golden/storm/approval_process.md. See the seams CLI; the viewer draws the same timeline in its Storm section.
Why one graph pays
Two things fall out of the single graph that no toolchain of separate modelers offers:
- Navigation across notations, described above.
- Questions across layers. "Which screens can eventually write this data?" is one SPARQL property path through the seams. See Cross-layer queries.
Text and diagrams are co-equal projections of the graph, which is what keeps the classic objections to visual programming at bay: Turtle diffs and merges like code, and the diagrams are drawn from it rather than stored beside it.
The project outline sets out the full motivation, the landscape and the design.