Skip to content

fix(strategy): expand reduce block to a task step - #39

Open
raskevichai wants to merge 1 commit into
nullclaw:mainfrom
raskevichai:fix/strategy-reduce-step-type
Open

fix(strategy): expand reduce block to a task step#39
raskevichai wants to merge 1 commit into
nullclaw:mainfrom
raskevichai:fix/strategy-reduce-step-type

Conversation

@raskevichai

Copy link
Copy Markdown
Contributor

Closes #33.

Root cause

buildReduceStep (src/strategy.zig) sets the synthetic step's type to "reduce":

try new_obj.put(allocator, "type", std.json.Value{ .string = "reduce" });

StepType (src/types.zig) has seven variants - task, route, interrupt, agent, send, transform, subgraph - and none of them is reduce. validateStepsForCreateRun runs on the expanded steps, so every POST /runs carrying strategy: "parallel" plus a reduce block is rejected with 400 {"code":"bad_request","message":"unknown step type"} before the run is created. The reduce block is documented in the README and used by examples/multi-agent-slack/workflows/parallel-research.json, so the documented path never worked.

Why task and not a new enum variant

The issue lists two options. This PR takes the second.

Adding reduce to StepType only moves the failure: the engine's node dispatch (engine.zig) branches on route, interrupt, transform, task/agent, subgraph and send, and there is no handler that reads a reduce type anywhere in src/. The only other occurrences of the word are state reducers, which are an unrelated concept. A run would pass validation and then fall through dispatch.

A reduce block carries worker_tags and a prompt_template, and its join is already expressed by the depends_on array buildReduceStep builds over every sibling step. That is exactly a task, so the synthetic type is now task and the fan-in stays where it belongs - in the edges, not in the type.

Changes

src/strategy.zig - the emitted type moves to a named reduce_step_type constant so the reason it is not "reduce" is recorded next to the decision. Nothing else in the expansion changes; the copied user fields, the default __reduce id and the depends_on construction are untouched.

Tests

Three added, two updated.

Added:

  • API: create run accepts parallel strategy with reduce block - drives the issue's payload through handleRequest and expects 201. This is the reported defect end to end.
  • expandStrategy: reduce step type is a known StepType - asserts every expanded step resolves through StepType.fromString, so a future synthetic type that the enum does not know fails here rather than at an HTTP boundary.
  • expandStrategy: reduce step preserves user fields - prompt_template, output_key and worker_tags survive the type rewrite.

Updated: the two existing reduce tests asserted the "reduce" type string and now assert "task".

Sanity check: restoring "reduce" makes all four reduce tests fail, the API one with expected 201, found 400 - the exact response from the issue.

Verification

zig build test --summary all   # 358/358 passed  (355 before, +3 from this PR)
zig fmt --check                # clean

The e2e suite (tests/test_e2e.sh) was not run; it needs Python mock workers and a live server. The change is covered at the API layer instead.

Scope

Not included:

One related defect found while confirming the above, left for a separate change because a rename does not fix it: expandStepNested emits type: "sub_workflow" for a step that carries both strategy and steps, and sub_workflow is not in StepType either, so nested strategies fail validation with the same "unknown step type". Retyping it to subgraph is not sufficient - the engine's subgraph node resolves a stored workflow by workflow_id, while the expansion builds an inline workflow object, so it would then fail at dispatch with "subgraph missing workflow_id". Making nested strategies work needs a decision on whether to persist the inline workflow and reference its id, or to teach the subgraph node to accept an inline definition.

buildReduceStep set the synthetic step's type to "reduce", but StepType has
no such variant, so validateStepsForCreateRun rejected every parallel run
carrying a reduce block with HTTP 400 "unknown step type".

No node handler anywhere reads a "reduce" type - the engine dispatches
task/agent, route, interrupt, transform, subgraph and send - so adding the
variant to the enum would only move the failure from validation to dispatch.
A reduce block carries worker_tags and a prompt_template and is joined to
its inputs by the depends_on edges buildReduceStep already builds, which is
exactly a task.

Closes nullclaw#33.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: parallel strategy reduce block fails with 'unknown step type'

1 participant