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