Files
lda-wf/docs/superpowers/specs/2026-05-17-reducer-capabilities-design.md
T

2.8 KiB

Reducer Capabilities Design

Goal

Make state merging a single capability system instead of keeping a built-in merge_strategy path beside future custom reducers.

Decision

StateField should reference exactly one reducer:

class StateField(BaseModel):
    type: str
    reducer: str = "wf.std.replace"
    trace: bool = True
    default: Any = None

The current built-ins become the first reducer library:

  • wf.std.replace
  • wf.std.append
  • wf.std.merge_object

There is no separate merge_strategy field after this migration.

Reducer Contract

Reducers are pure merge functions:

current_value, incoming_value -> merged_value

They do not receive node ids, frame ids, paths, timestamps, or other execution context. If behavior needs workflow context, it belongs in nodes or graph structure instead.

Reducers are named and resolved at runtime from a registry. Workflow artifacts store the reducer name, not a Python callable.

Runtime Model

wf_core owns:

  • a reducer callable protocol/type
  • a reducer registry
  • default registration of the three built-ins
  • lookup and execution during state writes

Missing reducer names are execution errors. Reducer failures are wrapped with the destination path so the failing state write is obvious.

Authoring Model

wf_authoring.state_field() changes from:

state_field(merge_strategy="append")

to:

state_field(reducer="wf.std.append")

Nested authored state projection continues to flatten exact state paths and now copies reducer references onto those flattened fields.

Why Reducer-Only

Keeping both merge_strategy and reducer would create two concepts for the same job. Turning the current built-ins into reducers gives us:

  • one merge abstraction
  • source-owned reusable behavior
  • inspectable future reducer libraries
  • a direct path to custom reducers such as wf.std.max, wf.std.set_union, or user-authored reducers

Error Handling

  • unknown reducer name: execution error before the state write commits
  • reducer rejects a value shape: execution error from that reducer
  • reducers remain pure, so there is no side-effect rollback problem

Compatibility

This is an intentional model migration:

  • core StateField.merge_strategy is removed
  • authoring state_field(merge_strategy=...) is removed
  • docs and tests migrate to reducer references

The project is still early enough that keeping both public shapes would create more confusion than value.

Testing

Tests should prove:

  • wf.std.replace preserves current replace behavior
  • wf.std.append preserves current append behavior
  • wf.std.merge_object preserves current shallow object merge behavior
  • exact nested state paths still select their own reducer
  • unknown reducers fail clearly
  • authoring metadata projects reducer names through nested state schemas