ADR-0005: Prefer Reactive Semantics Over Imperative Orchestration
- Status: Accepted
- Date: 2026-05-19
Context
A graph write in runex is rarely the end of the story.
After data enters the graph, useful behavior often follows:
- derive links
- extract topics
- advance lifecycle state
- create related nodes
- trigger writeback
There are two broad ways to implement this.
Option A: Imperative orchestration
Each ingest path, UI path, or command path explicitly calls follow-up logic in sequence.
Option B: Reactive semantics
Store mutations emit typed events. Actions match triggers on those events. Follow-up behavior is driven by the runtime's reactive bus.
runex now implements the second model, but this is important enough to record as a durable architecture choice.
Decision
We prefer reactive semantics over imperative orchestration for business behavior that follows from graph mutation.
This means:
- the primary entry point is "write to the graph"
- downstream business behavior should be expressed as machines and actions over store events
- imperative dispatch still exists, but it is not the main way to keep semantics coherent across the system
The preferred framing is:
the graph write is the first cause; semantic cascades follow from the runtime, not from call-site choreography
Why
Imperative orchestration duplicates semantic logic across call sites.
If each ingest path or product feature manually performs follow-up steps, the system becomes fragile:
- one caller forgets a step
- another caller runs steps in a different order
- a new source needs custom orchestration code
- semantics drift between sources, UI paths, and batch jobs
Reactive semantics keeps the rule attached to the data shape and event that should cause it, not to the code path that happened to perform the first write.
That is a better fit for a graph-shaped ontology runtime.
Alternatives Considered
1. Imperative orchestration in the ingest pipeline
Rejected.
Reason:
- the pipeline should stop at
CanonicalItem -> Store - once it starts owning downstream business semantics, every source path becomes a special orchestrator
- that weakens reuse and increases drift
2. Imperative orchestration in product/UI code
Rejected.
Reason:
- product shells should not own core business truth
- the same semantic behavior would need to be reimplemented per shell
- agents and UIs would stop sharing the same causal model
3. Mixed ad hoc orchestration plus some reactive rules
Rejected as the default.
Reason:
- it is acceptable only in narrow exceptional cases
- as a norm, it creates ambiguity about where semantics live
- debugging becomes harder because the same outcome may originate from two architectural styles
Consequences
Positive
- one write path can trigger consistent semantics regardless of source
- business rules stay attached to data and event triggers
- ingest paths remain simpler and more reusable
- event logs become meaningful causal traces of what happened and why
Negative / Tradeoffs
- runtime semantics become more abstract than direct imperative code
- authors must learn to think in triggers, guards, and effects
- some debugging requires tracing cascades rather than reading one imperative function top to bottom
We accept this because the coherence and reuse benefits are structural.
Implications
- new derived behavior should default to
.scmactions and machines - ingest code should resist growing business-specific follow-up steps
- shells should issue writes and observe events, not choreograph the semantic cascade themselves