Skip to main content

Concepts · 5 of 6

Where Loom ends

Loom proposes an action; the Commission runtime rechecks it and invokes it through an effect port the embedder supplies.

ShippedAtlas ADR 0082, decided 2026-10-04, which amends ADRs 0070 and 0071; crates/loom-commission/src/ports/effect.rs, crates/loom-commission/src/runtime.rs, crates/loom-commission/src/ports/connector.rs, crates/loom-connectors/src/lib.rs

Loom proposes. Commission invokes. Every executor, Loom included, ends a step by returning a ProposedAction: an action id and its arguments. Loom's side ends there. It makes no connector call, holds no handle to connectors, and receives no provider credential just because a connection exists.

pub enum ExecutorOutcome {
ProposedAction(ExecutorOutcomeProposedAction),
NeedsHumanJudgment(ExecutorOutcomeNeedsHumanJudgment),
Suspended(ExecutorOutcomeSuspended),
NoUsefulAction(Unit),
CompletedLocalReasoning(Unit),
CaseMoved(ExecutorOutcomeCaseMoved),
}

Immediately before the effect, the Commission runtime rechecks the frontier, the case revision and authority. What it admits becomes an AdmittedRequest, which only the runtime can build, and goes to the embedder's effect port:

pub trait EffectPort {
fn performs(&self, action: &str) -> bool;
fn invoke(
&self,
commission: &Commission<commission_state::Assigned>,
request: &AdmittedRequest,
) -> Result<EffectOutcome, EffectError>;
}

The alternative, Loom making the call itself, was rejected: it saves a hop, but every other executor, such as a human tool or a workflow, would need its own effect path.

What holds today​

  • The executor returns a ProposedAction and invokes nothing.
  • An action that needs authority is proposed too. Loom never suspends for authority: the runtime rechecks the proposal and asks its authority provider.
  • run_until_blocked invokes each admitted request once, and moves the case to a new revision only after an effect the port reports as Performed. A request the port answers Pending is invoked again at the next iteration, revalidated first, until the port answers otherwise.
  • The slice's LocalEffects performs software.change/1 actions in a git work tree. It never merges, pushes or deploys.
  • Commission's ConnectorEffects performs the actions a commission binds to Connector operations, through a ConnectorInvoker. b10x-loom-connectors (loom_sdk::connectors) fills that port over a Connectors service, pinned at Connectors v0.35.0. The host declares one ConnectorEndpoint per Connector instance (ess/commission/domains/responsibility.yaml): its URL, a credential reference its own resolver turns into the service credential, and whether plain http is admitted. Each binding declares its operation's effect: Write for an operation the service describes with the mutation profile, Read otherwise. ConnectorsInvoker describes the endpoint, invokes the bound operation once on POST /v1alpha2/invoke, never falls back and never resends.
Connectors answersThe invoker answers
a write's success, with the attempt it recordedPerformed, naming that attempt
a read's success, with no attempt and its audit record completePerformed, naming that audit record
an error whose recorded attempt is refused or not_attemptedRefused
anything else: a write's success naming no attempt, a read's success with an attempt or an audit record that is not complete, an error without a recorded attempt, an unknown outcome, a protocol or transport failureErr

An instance with no declared endpoint, a credential that does not resolve, a service that describes another instance, and a binding whose effect is not the one the service describes are Err before the operation is invoked.

Planned

An effect port for Substrate is not decided. Whether a released Connectors provider offers the file-edit and test-run operations a coding phase needs is not known yet.