Files
lda-wf/docs/superpowers/specs/2026-07-22-atomic-step-input-bindings-design.md
T

12 KiB

Atomic Step Input Bindings Design

Goal

Expose the canonical step input-binding list through focused draft authoring so an agent or operator can assemble one structured node input from graph paths and literal values in one revision-checked edit.

Problem

The runtime and persisted workflow model already represent node input assembly as an ordered list of InputPathBinding | InputValueBinding. Each binding writes one graph-sourced or literal value to a node-local path, and several bindings can therefore build one nested input object.

Focused draft authoring narrows this list into a source-to-target dictionary. That surface cannot express literal values or one source feeding several local targets. Building a structured input across several focused bind commands also advances the draft revision after every field and can leave an incomplete authoring state between commands.

The missing concept is not a new runtime binding. It is one atomic authoring operation over the canonical binding list.

Terms

  • Step input binding is the existing canonical InputPathBinding or InputValueBinding stored on a node-like step.
  • Structured input assembly is the runtime result of applying several step input bindings to nested node-local targets.
  • Binding replacement replaces the complete input-binding list for one step. It does not merge bindings by source or target.

Do not introduce a CompositeBinding model. Composition is behavior produced by the existing binding list, not a separate persisted domain object.

Scope

This slice adds an atomic, focused operation for replacing the complete input bindings of one capability-backed draft step. It supports:

  • several graph sources assembling one nested node-local object;
  • one graph source feeding several distinct node-local targets;
  • literal JSON values, including explicit null;
  • nested and whole-payload node-local targets;
  • authoring-time workflow input/state schema projection from the capability input contract;
  • local API, JSON-RPC, remote client, MCP, and CLI access.

The stored workflow shape, core runtime behavior, and canonical path models do not change.

Out Of Scope

This slice does not:

  • add incremental add/update/remove semantics to the canonical operation;
  • add revision amend, rebase, fast-forward, or history semantics;
  • change step output bindings or workflow output bindings;
  • solve output fan-out or literal workflow outputs;
  • add focused step metadata or update-step operations;
  • add TypeScript JSON-RPC parity or code generation;
  • introduce a structured template language such as $path expressions;
  • implement general JSON Schema reference resolution or schema assignability.

Canonical Interface

Add the following method to WorkflowApiSurface and its local implementation:

async def set_step_input_bindings(
    *,
    workspace_id: str,
    revision: int,
    step_id: str,
    bindings: Sequence[InputBinding],
) -> dict[str, Any]: ...

Expose the same request through:

  • JSON-RPC method workflow.draft_workspaces.set_step_input_bindings;
  • RpcWorkflowApiClient;
  • the MCP workflow surface.

Transport models reuse wf_core.models.steps.InputBinding. They must not define parallel path/value binding models.

The operation replaces the complete ordered list. It preserves caller order in the stored draft. Repeated graph source paths are valid when their local targets are distinct. Equal or ancestor/descendant local targets overlap and are rejected, regardless of list order.

Example request bindings:

[
  {"path": "state.report.title", "target": "request.title"},
  {"path": "state.report.markdown", "target": "request.body"},
  {"value": "markdown", "target": "request.format"}
]

Revision And Mutation Semantics

The operation follows the established semantic-authoring precedence:

  1. Validate the request envelope and canonical binding shapes.
  2. Load the workspace and compare the expected revision.
  3. Return the canonical revision_conflict result immediately when stale.
  4. Resolve the current step and capability contract.
  5. Validate binding semantics and compute schema projections.
  6. Replace the binding list and projected schemas through one JSON Patch call.
  7. Let the mutation-time revision guard protect against races after preflight.

No partial patch is persisted when any binding fails. A changed replacement creates exactly one new draft revision. After revision and semantic validation, an exact replacement whose canonical bindings and projected schemas equal the stored values returns the current workspace summary without advancing the revision.

Semantic Validation

The authoring operation resolves the capability-backed step and validates the complete replacement before mutation.

Local Targets

Every binding target must resolve against the capability input schema. Nested targets may traverse bounded local #/$defs/... or #/definitions/... references. The whole-payload target . addresses the complete capability input schema.

Targets must not overlap. For example, request and request.title cannot appear together because the runtime would otherwise make the result depend on write order. Two bindings with the same target are also overlapping. The same source may feed request.title and audit.title.

Path Bindings

InputPathBinding.path continues accepting canonical workflow-readable input.*, state.*, and context.* paths.

For input.* and state.* sources:

  • reuse an already-declared workflow schema path;
  • when the source path is missing, project its schema from the corresponding capability-input target schema;
  • apply every projection to staged schemas and commit them with the binding replacement.

Existing source schemas are not subjected to a new assignability algorithm. They retain current bind behavior and remain subject to draft/runtime validation. context.* bindings are valid but do not project workflow schemas.

Literal Bindings

InputValueBinding.value accepts any JSON-compatible value, including null, arrays, and objects. Validate a literal against the capability schema selected by its local target before mutation. A literal targeting . must be an object that satisfies the complete capability input schema because the runtime whole payload operation requires a mapping.

Shared JSON Schema Operations

Deepen wf_api.schema_projection rather than adding traversal to draft authoring. Add one reusable schema-path selection/validation operation that:

  • selects inline nested schemas;
  • follows bounded local $defs and legacy definitions references;
  • retains enough root definition context to validate selected fragments;
  • rejects remote, unresolved, cyclic, and non-object intermediate references precisely;
  • supports the existing projection helpers and literal validation.

The exact private/public helper split is an implementation choice, but schema path traversal, local-reference handling, projection, and literal validation must remain localized in wf_api.schema_projection.

CLI Design

Keep the existing command name:

wf draft set-input WORKSPACE --revision N --step STEP ...

Preferred canonical replacement forms are:

wf draft set-input WS \
  --revision 4 \
  --step publish \
  --map state.report.title=request.title \
  --map state.report.markdown=request.body \
  --value request.format='"markdown"'

and:

wf draft set-input WS \
  --revision 4 \
  --step publish \
  --bindings-file bindings.json

CLI behavior:

  • repeatable --map SOURCE=LOCAL_TARGET creates path bindings;
  • repeatable --value LOCAL_TARGET=JSON creates literal bindings;
  • --bindings-file accepts the exact canonical JSON array;
  • --bindings-file is mutually exclusive with --map and --value;
  • --clear explicitly replaces the list with [];
  • no bindings and no --clear is an input error;
  • the canonical replacement path does not support --merge.

--bindings-file preserves exact list order. Convenience flags serialize path bindings in --map order followed by literal bindings in --value order. Because overlapping targets are rejected, this cross-kind ordering cannot change the assembled payload.

The existing map-only API/RPC operation and its merge behavior remain as a compatibility adapter for real callers. Existing CLI --merge may continue to delegate to that adapter only for map-only requests; it must reject combinations with --value, --bindings-file, or --clear. New help and agent instructions describe replacement as the preferred behavior.

Current bindings remain exportable without another endpoint:

wf draft inspect WS --include-draft |
  jq '.draft.steps.publish.input' > bindings.json

Error Contract

Semantic errors identify the binding index and complete canonical path where possible. Representative messages are:

  • bindings[1].target 'request.missing' is not declared by capability ...;
  • bindings[0].target 'request' overlaps bindings[1].target 'request.title';
  • bindings[2].value does not satisfy the schema at 'request.format';
  • bindings[0].path 'input.report.title' cannot be projected from target ....

Malformed canonical binding objects fail request-envelope validation. After that validation, stale revision wins over missing steps, unknown capabilities, schema lookup, overlap, and literal-value errors. All failures leave the draft document and revision unchanged.

Testing

Schema Projection

Add focused tests for:

  • selecting inline and nested schema paths;
  • selecting paths through $defs and definitions references;
  • preserving definition context during literal validation;
  • valid and invalid literal scalars, objects, arrays, and explicit null;
  • remote, unresolved, cyclic, and non-object intermediate references.

Python Authoring

Add API tests for:

  • several path bindings assembling one nested capability input;
  • path and literal bindings in one replacement;
  • one source fanning out to several targets;
  • explicit null and whole-payload bindings;
  • stable binding order in the stored draft;
  • input and state schema projection across several bindings;
  • context bindings without schema projection;
  • missing, duplicate, and ancestor/descendant targets;
  • invalid literals;
  • stale revision precedence and no mutation on every failure class;
  • replacement rather than merge semantics;
  • compatibility of the existing map-only operation.

Compile the resulting draft and execute one representative workflow to prove the runtime receives the intended nested input object.

Transport And CLI

Add tests proving:

  • JSON-RPC, remote client, and MCP preserve canonical path/value unions and binding order;
  • CLI --map and --value compose one replacement request;
  • --bindings-file round-trips an exported inspected list;
  • --clear is explicit and an empty accidental invocation fails;
  • canonical replacement rejects --merge combinations;
  • the legacy map-only merge path remains operational;
  • expected errors remain compact through local and remote CLI targets.

Documentation And Issue State

After verification:

  • check the atomic structured node-input assembly issue;
  • check the focused literal node-input binding issue;
  • leave input/output fan-out map loss open unless the implementation also migrates every lossy reader and writer to canonical lists;
  • leave workflow-output literals, nested workflow-output projection, focused step updates, and TypeScript parity open;
  • update wf_cli and agent skill documentation with replacement, literal, and export examples;
  • add a completed roadmap entry linking to the archived implementation plan;
  • archive the implementation plan under docs/historical/superpowers/plans/.

Success Criteria

  • One focused operation replaces a step's complete canonical input-binding list in one revision.
  • Structured nested assembly, path fan-out, literal values, explicit null, and whole-payload targets use the existing runtime model unchanged.
  • All local targets are validated against the capability input schema before mutation.
  • Missing input/state source schemas are projected atomically with the binding replacement.
  • Existing map-only callers retain their current behavior through a compatibility adapter, while new callers use canonical bindings.
  • No new persisted binding type or template language is introduced.
  • Focused API/RPC/MCP/CLI tests, Ruff, formatting, basedpyright, compile, and runtime regressions pass.