ADR-0002: Preserve the Capability-vs-Channel Boundary
- Status: Accepted
- Date: 2026-05-19
Context
runex must connect to changing external systems while preserving a stable runtime core.
The architecture now distinguishes:
- capabilities / kernels
- inbound channels (
DataSourceSpec) - outbound channels (
SinkSpec) - the typed
CanonicalItemboundary between external systems and the graph
A recurring alternative is to collapse adapters into kernels. Typical forms include:
discover-*kernels returning item payloads- generic
upsert-itemkernels receiving those payloads - treating all ingest connectors as just another kernel family
This appears to reduce concepts, but it changes the structure of the system in important ways.
Decision
We preserve the boundary:
- Capability / kernel = stable, core-owned effect surface
- Channel = high-churn external connector that translates
raw <-> CanonicalItem
Inbound channels remain explicit DataSourceSpec objects. Outbound channels remain explicit SinkSpec objects.
Adapters are not to be collapsed into kernels.
The runtime continues to enforce:
- channels declare
requires - capabilities are injected by the broker
- channels perform zero direct I/O
CanonicalItemis the single typed boundary in both directions
Why
The adapter concept is structural, not accidental.
External systems are heterogenous and unstable. The runtime's internal world is typed and uniform. A translation layer between those two worlds cannot be eliminated; it can only be made:
- explicit and governable
- or implicit and scattered
DataSourceSpec / SinkSpec are the explicit form of that translation layer.
Alternatives Considered
1. Collapse all adapters into kernels
Rejected.
Reason:
- the adapter concept does not disappear; it becomes hidden inside kernel bodies or side registries
- this removes a clean runtime concept and replaces it with implicit convention
2. Let the DSL move item JSON between kernels
Rejected.
Reason:
- it demotes
CanonicalItemfrom typed boundary to payload convention - it turns the DSL into raw data plumbing
- it weakens the machine-first contract
3. Keep adapter logic but remove DataSourceSpec
Rejected.
Reason:
- the metadata still has to live somewhere:
- params
- watchability
- summary
- ontology hints
- capability requirements
- without
DataSourceSpec, that metadata drifts into docs, ad hoc dicts, UI code, or docstrings - this is not simplification; it is concept leakage
Consequences
Positive
- preserves
manifest().data.datasourcesandmanifest().data.sinksas complete introspection surfaces - preserves
CanonicalItemas the single typed ingress/egress boundary - isolates external churn at the channel edge
- keeps kernel/capability vocabulary small and stable
- keeps the DSL focused on business semantics rather than payload transport
Negative / Tradeoffs
- the system keeps more than one explicit extension concept (
KERNELS,DataSourceSpec,SinkSpec) - authors must learn the difference between capabilities and channels
We accept this cost because the concepts correspond to real structural differences in the system.
Design Rule
When deciding where new logic belongs, ask:
- does it perform syscall / socket / subprocess / direct external I/O?
- then it is capability territory
- does it translate one external world's shape into or out of
CanonicalItem?- then it is channel territory
This is an I/O-boundary decision, not a typing-boundary decision. Both sides stay typed.
Implications
- new folder-based ingest features should be authored as source extensions first, not as kernels
- desktop products should render channels as user-connectable things, not expose raw kernels as if they were connectors
- future architecture work should treat "adapter elimination" proposals skeptically and ask where the translation concept is merely being hidden