1/2 Flat topology — core seam: batch-branch router hook + frozen fan-out bound - #1088
1/2 Flat topology — core seam: batch-branch router hook + frozen fan-out bound#1088lchoquel wants to merge 3 commits into
Conversation
…nto the run Two small, backend-neutral changes to the pipe-run seam, both prerequisites for a distributed backend that runs a controller tree inline instead of one workflow per sub-pipe dispatch. `PipeRouterProtocol.run_batch_branch` is a second dispatch entry point, called by PipeBatch for each per-item fan-out branch. It is the only signal a router can get that a dispatch is a batch branch: the branch job carries the branch pipe and the item's memory, which is byte-for-byte the shape of any other dispatch. The default body delegates to `run` — so in-process execution is unchanged, every existing router implementation keeps working untouched, and batch branches still pass through the observer hooks. Core's own PipeRouter deliberately does not override it. `PipeRunParams.batch_max_concurrency` freezes `pipeline_execution_config.max_concurrency` onto the run at construction, so PipeBatch stops reading live config at fan-out time. That read was a genuine durable-execution hazard: the bound is also the chunk size that decides where a backend's task boundaries fall between branch dispatches, so a worker redeploy mid-run could make a replay group its dispatches differently from the recorded history. `resolve_batch_max_concurrency` moves next to the factory that now owns the read. Core docs stop asserting a Temporal-specific topology (one child workflow per controller dispatch, `WfPipeRouter` by name) — how much of a controller tree a backend spreads across durable units is that backend's call, not core's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JenThPdUXSpL4SzY5zyqnM
Confidence Score: 4/5The PR is not yet safe to merge because previously compatible duck-typed routers can still fail on the first batch branch dispatch. PipeBatch unconditionally invokes Files Needing Attention: pipelex/pipe_controllers/batch/pipe_batch.py and pipelex/interpreter_hub.py Reviews (2): Last reviewed commit: "fix(pipe-run): stamp the running pipe on..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d8a697206
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
All reported issues were addressed across 14 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Repo rule, flagged by the PR reviewers. The module docstring already framed both halves as one subject, so the merge reads naturally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JenThPdUXSpL4SzY5zyqnM
|
Thanks — worked through all of these. They dedupe to four distinct items; one is fixed, one is deferred with a written note, two are dropped. Reasoning below so the drops are auditable. 1.
|
`live_run_pipe` builds a child `JobMetadata` carrying `pipe_code=self.code`; `dry_run_pipe` passed the caller's metadata straight through. So for the whole of a dry run `job_metadata.pipe_code` stayed whatever came in — usually unset — and every consumer that identifies a step by it saw an anonymous step in DRY and a named one in LIVE. Telemetry deliberately stays live-only: `pipe_run_id` and a real span belong to a real run. `otel_context` is cleared rather than inherited, which is what that required parameter exists to force — a dry step must not attach to a live span. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@greptileai @cubic-dev-ai please review the newly pushed commit |
@lchoquel I have started the AI code review. It will take a few minutes to complete. |
First of two PRs for the flat-workflow-topology reshape. This one is core-only and stands on its own; the second lives in the closed
pipelex-temporalrepo and depends on this landing (it pins this branch's rev today, and will move to the released core version).What this is for
The Temporal backend currently starts one child workflow per sub-pipe dispatch — a sequence of five steps is five nested workflows, each paying a full crate load, memory dehydrate/rehydrate, and history round trip. The reshape runs the whole controller tree as inline workflow code and keeps child workflows only where they earn their keep:
PipeBatchfan-out branches, which want per-item isolation and history partitioning.That needs exactly two things from core, both backend-neutral.
1.
PipeRouterProtocol.run_batch_branchA second dispatch entry point, called by
PipeBatchfor each per-item branch.It exists because the batch branch is the one dispatch in the pipe tree whose semantics its
PipeJobcannot express: a branch job carries the branch pipe and the item's memory, which is byte-for-byte the shape of any other dispatch. Without a distinct call site, a distributed router cannot tell a batch branch from a sequence step.The default body is the behavior for in-process routers — a branch is just a run. Core's
PipeRouterdeliberately does not override it, every existing router implementation is untouched, and nothing about direct execution changes. Note it delegates torun, not_run_pipe_job: that keeps batch branches inside the observer stream, and there is a test pinning it.Alternatives considered and rejected: a
dispatch_hintfield onPipeJob(a wire-visible DTO change for a hint that never crosses a process boundary), and a ContextVar scope around the branch dispatches (must apply to exactly one dispatch depth, so a sequence inside a branch must not inherit it — implicit and fragile where the hook is explicit and local).2. Freeze the batch fan-out bound onto the run
PipeBatchreadpipeline_execution_config.max_concurrencyfrom live config at fan-out time. That is a real durable-execution hazard, and a pre-existing one —PipeBatchalready runs inline inside a workflow today:The setting is now resolved once, at run-params construction, and carried as frozen
PipeRunParams.batch_max_concurrency(None= unbounded,gather_bounded's own sentinel).PipeBatchno longer importsget_configat all;resolve_batch_max_concurrencymoves next to the factory that owns the read. Nested batches inherit it through the existingmodel_copy.Breaking only for code that mutated
max_concurrencymid-run and expected the change to take effect.Tests
_run_pipe_jobwith the job it was handed, and goes throughrunso observers still fire.resolve_batch_max_concurrencytranslation table (moved with the helper).max_concurrency = 2, still 2 after the config flips to 5.Docs
The router-SPI page gains a "batch-branch hook" section; the Orchestrator SPI table row names it. The PipeBatch concurrency page documents the freeze.
Two spots in
pipe-routing-and-execution.mdasserted a Temporal-specific topology — the controller arm of the distributed sequence diagram, and a paragraph promising "each child pipe in a controller gets its own workflow boundary", plusWfPipeRouterby name. How much of a controller tree a backend spreads across durable units is that backend's call, not core's, and the reshape makes those claims false. Rewritten to say the backend decides. That is why the docs diff is larger than "document the hook" implies.Housekeeping
Moving
test_pipe_batch_concurrency.pyleft four dead entries in the committed.test_durations, which its own guard test catches. Dropped surgically (four lines, original formatting) rather than regenerating the whole file.🤖 Generated with Claude Code
https://claude.ai/code/session_01JenThPdUXSpL4SzY5zyqnM
Summary by cubic
Adds a
run_batch_branchhook toPipeRouterProtocol, freezesPipeBatchfan‑out concurrency onto the run, and fixes dry‑run metadata so steps are correctly identified. This enables flat-topology backends and removes a durable-execution replay hazard without changing in-process behavior.New Features
PipeRouterProtocol.run_batch_branch: called byPipeBatchfor each per‑item branch; default delegates torun(observers still fire) so existing routers stay unchanged, while distributed routers can override to isolate branches.Bug Fixes
PipeRunParams.batch_max_concurrency(None= unbounded).PipeBatchstops reading livepipeline_execution_config.max_concurrency, preventing replay divergence on durable backends. Breaking: mid‑run changes tomax_concurrencyno longer affect in‑flight batches.JobMetadata(pipe_codeset,otel_context=None) so step identification matches live runs without attaching to a live span.Written for commit 7d3ad12. Summary will update on new commits.