Repository navigation
refactor(replace): run Substitute as one invocation - #336
Merged
binaryaaron merged 6 commits intoOct 29, 2026
Merged
binaryaaron merged 6 commits into
binaryaaron merged 6 commits into
Conversation
Substitute lowers the input frame once and declares one workflow: an invocation-scoped map step over all targets, then replace and verify-patches per target. A row the map step drops fails at that step and is absent from the output. The runner raises the first error recorded by a non-verifier step, so a map-step or replace exception still reaches the caller. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Two Substitute runner tests fed an input frame with empty entities while the mocked map frame carried the stub entities. The route now lowers the input frame, so the two frames must agree. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
…odule Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
* test: pin interrupt propagation and in-call cancellation in the spec programs Add E11.6 and E11.7 to the cancellation program and E14.9 and E14.10 to the invocation-operation program. They pin that an interrupt raised by a callback reaches the caller of protect(), and that a cancel() issued inside a callback lets that call finish but stops the next dispatch. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com> * refactor(graph): run the kernel's node sequence on mutable state State holds rows and entries in dispatch order and changes in place through apply(), a match over the event classes. The executor loops over nodes in order and, within a node, over units in order; per-target and invocation-scoped nodes settle results through one path. Plan.deps and the persistent vector are gone. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com> * refactor(graph): type the kernel's statuses and seed sources Statuses are a StrEnum with the same string values; the ledger and attempts still emit plain strings. A row keeps seed values and node outputs apart, and a plan source names either a seed or a node output. CancelToken gains link() so run_in_frame no longer writes a private attribute, and compile_sequence validates kinds without a bare side-effect loop. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com> * refactor(graph): simplify the kernel's events, readiness and frame tokens Events become NamedTuples, readiness drops the redundant pending check, frame tokens link through one call, and docstrings shrink. Behaviour is unchanged. Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com> * refactor(graph): spell out the kernel's dispatch paths execute now has one branch for invocation-scoped nodes and one for per-target nodes, each calling its own helper. _run_invocation handles a raised call, a wrong-shaped return and per-entry results as separate cases, and the dense one-liners in compile_sequence, _inputs and apply are unfolded. Behaviour is unchanged. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com> * test: pin cancellation raised just before an invocation-scoped step Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com> --------- Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com> Co-authored-by: Claude <noreply@anthropic.com>
…/chore/graph-sdk-substitute-one-invocation 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
…/chore/graph-sdk-substitute-one-invocation 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Base automatically changed from
aagonzales/chore/graph-sdk-invocation-operation
to
aagonzales/graph-sdk/feat/main
October 29, 2026 21:15
binaryaaron
deleted the
aagonzales/chore/graph-sdk-substitute-one-invocation
branch
October 29, 2026 21:15
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Substitute now runs as one call to the graph kernel instead of two. Its whole-frame replacement map becomes the first
step of the per-row workflow, so a row that DataDesigner drops is recorded as a failed target in the same record as
every other row. Outputs, errors and failed-record accounting for
Anonymizerusers are unchanged apart from thetrace-column dtypes listed below.
This PR also carries the kernel executor rewrite merged in from #337 (section below).
This branch stacks on #332 and is rebased onto its base branch when the stack moves.
What changes
engine/replace/route.py).run_substitute_routelowers the input frame once and runssubstitute-map,replaceandverify-patchesin oneprotect()call.substitute-mapis an invocation-scopedoperation: it calls
LlmReplaceWorkflow.generate_map_onlyonce over the whole frame, maps the returned rows back totheir input positions with
resolve_source_positions, and returns each row's replacement map. A row DataDesignerdropped gets
Noneand fails at that step; its later steps are blocked.replacereads the map from the graph. When a workflow has a map step,replacetakes the map from itsreplacement_mapinput; Redact, Annotate and Hash still build their maps locally and behave as before.error from
generate_map_onlyor from applying replacements still reaches the caller once, as the same object.the replacement map, map source, replaced text and application columns read from the record.
failed_recordsstillcomes from
generate_map_only.tests/engine/test_replace_runner.pygave the runner an input frame with no entities whilethe mocked map result carried entities. The input frames now carry the same entities as the mocked result.
Net non-blank, non-comment code lines under
src/: +24, all inroute.py.Known differences
trace_dataframe,_validated_entitiesand_augmented_entitieskeep the dtype they have in theinput frame (a pyarrow struct type), where main has
object. Their values are unchanged.generate_map_onlyis no longer called; the result is still an empty frame with nofailed records.
Checks
make checkpasses.make test: 1,557 passed.test_wandb_snapshot_rejects_fifo_socket_and_devicefails in the test environment withAF_UNIX path too long, as on the base.uv run --group dev python -m tests.spec_programs.run_all: 70 pass, 8 retired, 0 fail.f4f2b73d) on pandas 2 and 3, over all cases: no blocking difference beyond the differencesthe base already has; the dtype difference above is the only new non-blocking one.
and 1.22×).
Kernel executor rewrite (from #337)
The graph kernel's executor is rewritten to run its node sequence directly on mutable state, with structural pattern
matching for events and per-target results. Behaviour is unchanged: dispatch order, outcomes, ledgers, attempts,
recorded errors, rejection codes and cancellation all match the base.
kernel.pygoes from 333 to 272 code lines, andfive new spec-program checks pin interrupt propagation and cancellation from inside a callback.
What changes
tests/spec_programs/). Two lifecycle checks and three invocation-scoped checks pin behaviour noprogram covered (four are added before the rewrite, one after it):
KeyboardInterruptfrom a per-target callback reaches the caller ofprotect()as the same object, and latertargets are not dispatched;
KeyboardInterruptfrom an invocation-scoped callback reaches the caller;engine/graph_core/kernel.py). The persistent vector, the linked-list event log and the copiedstate objects are gone.
Stateholds a list of rows and a list of entries, andapply(state, event)changes it inplace with one
matchover the event types. Its guards are unchanged: a dispatch to a node that is not pending, orafter cancellation, raises; a completion or failure for a node that is not running is ignored; a second close is
ignored.
executeruns the nodes in order, each over every ready unit in order. The storedscan cursor,
next_readyand the per-node dependency sets are removed; a unit is ready at a node when it is notclosed and every earlier node succeeded. Cancellation is checked before each per-target call and before each
invocation-scoped call, as before.
_run_targetmakes one call for one target;_run_invocationmakes one callfor every ready target. Both complete a target's output through the same helper, and an invocation-scoped callback's
per-target entries are settled by a
match:Nonefails the target withmissing_output, anExceptionfails it with
raised, and any other value is checked as the target's output.StrEnumwhose values are the previous strings, so ledgers and comparisons are unchanged.An input port's source is a
Source(node, port)named tuple; seed values are kept apart from node outputs instead ofsitting under node index
-1. Events are named tuples.compile_sequencechecks kinds without a side-effect loop;CancelToken.linkreplaces thedirect write to a token's parent from
run_in_frame;execution.pyfollows the renamedapplyand the new rowlayout.
Net non-blank, non-comment code lines under
src/: −58 (kernel.pyandexecution.pyonly).Checks
make checkpasses.make test: 1,557 passed.test_wandb_snapshot_rejects_fifo_socket_and_devicefails in the test environment withAF_UNIX path too long, as on the base.uv run --group dev python -m tests.spec_programs.run_all: 75 pass, 8 retired, 0 fail.protect()(dispatch order, interrupt identity,cancellation before, during and after a call, every invocation failure form, ledgers, attempts and values):
identical output at every step.
f4f2b73d) on pandas 2 and 3, over all cases: no blocking difference beyond the differencesthe base already has.
and 1.23×).
Main merged (5155d6d)
This branch now includes main at 5155d6d through its base branch, including the Rewrite join change described on the remove-old-sdk PR.
Main merged (f294dc1)
This branch now also includes main at f294dc1 (#328, recovery from incomplete replacements in Rewrite) through its base branch.
🤖 Generated with Claude Code