Skip to content

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 CanonicalItem boundary 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-item kernels 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:

  1. channels declare requires
  2. capabilities are injected by the broker
  3. channels perform zero direct I/O
  4. CanonicalItem is 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 CanonicalItem from 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.datasources and manifest().data.sinks as complete introspection surfaces
  • preserves CanonicalItem as 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:

  1. does it perform syscall / socket / subprocess / direct external I/O?
    • then it is capability territory
  2. 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