Skip to content

feat(graph): add typed workflow execution and evidence qualification - #302

Closed
binaryaaron wants to merge 34 commits into
mainfrom
codex/anonymizer-graph-sdk-contracts
Closed

binaryaaron wants to merge 34 commits into
mainfrom
codex/anonymizer-graph-sdk-contracts

Conversation

@binaryaaron

@binaryaaron binaryaaron commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

this is comically large and the result of a few attempts to do a heavy, semi-autonomous agent run. i will be making changes to remove larges swaths of code and weird tests from the repo, bc the initial +130k loc is absurd on its face.

Summary

This PR moves Anonymizer's production pipelines (Rewrite, detection, Substitute) from hand-written per-route controllers onto a typed Graph SDK, behind the unchanged Anonymizer API: the public DataFrame behavior is preserved under REQ 1 except the accepted deltas listed in the status block. DataDesigner execution stays behind NddAdapter.run_workflow().

anonymizer.graph is not a compatibility contract yet. Until the Graph SDK is published, its names, __all__ order, signatures, defaults, exception constructors, class pickles and import cost may change in any commit. Tests that pin them are refactor safety nets and are re-recorded with the change. The contract is the Anonymizer API and config of 0.4.0.

This branch (aagonzales/graph-sdk/feat/main) is the long-lived base of the Graph SDK feature line, not a merge candidate for now. Later work, including PR2, builds on it and lands here as PRs.

Done: Rewrite, detection and Substitute run graph-owned end to end, with their legacy controllers deleted; a scoped logging correction keeps record content out of library diagnostics. The history is squashed into 15 reviewable commits; #314–#316, a main merge and #318 followed. Evaluation, native admission and Session are PR2+.

For dogfooders and reviewers

This branch moves the production pipelines onto the Graph SDK while preserving the public DataFrame
API. Its history was rewritten once, on 2026-10-20, into 15 commits with the same tree as 2fc6d810; otherwise it
only fast-forwards. Test at the latest commit and state the SHA you ran in every comment (current pin: e0d8b317).
Setup (in this order, details in tools/dogfood/README.md):
uv sync --frozen --extra notebooks; start the detector server
(uv run python tools/serve_gliner.py --host 127.0.0.1 --port 8001 — no bearer token is needed unless you set
ANONYMIZER_LOCAL_GLINER2_TOKEN when starting it); export the provider key named in
tools/dogfood/providers.yaml (NVIDIA_INFERENCE_API_KEY; or edit the file for your provider). Smoke test:
pytest -m compat (~1 min), then python examples/graph_sdk/local_workflows.py (provider-free).
Dogfood kit: tools/dogfood/dogfood_run.py runs one route in one environment and writes a comparable
result bundle; tools/dogfood/compare.py diffs two bundles. Run the same route against the released wheel in
a second environment at matching versions (pandas==2.3.3, data-designer==0.9.4) and diff; against a
pandas 3 install the only expected differences are the trace-column dtype rows listed below.
How to approach it, in this order. No line-by-line review yet; the branch is being cut down substantially.

  1. Dogfood with any data, at the latest commit. Point your agent at this section and just verify it works
    on your end: detection+Redact, Substitute, Rewrite via run and preview, diffed against the released 0.4.0
    with tools/dogfood/. Highest-value surfaces: Substitute and Rewrite (replacement maps), custom
    detectors/runners, nullable or non-string columns, Ctrl-C mid-run. A one-line "clean at , pool "
    comment is as useful as a diff.
  2. Latency and quality benchmarks from the team's experiments repo at the same pin and model pool as your
    release baseline; post the deltas (instructions in the team channel).
  3. Build something small with the public SDK — for example, how you would adapt work you are doing now, or
    a case like integrating with a partner project — and tell us where the semantics or composability surprised
    you, where docs/concepts/graph-sdk.md and the behaviour disagree, and what you could not express. Tag
    [sdk-semantics]; those are not parked.
    What to look at: result.failed_records; trace_dataframe["_replacement_application"] — a non-zero
    skipped_span_count means original text survived; a null *_rewritten with needs_human_review=True is a
    rewrite WITHHELD by design, not a crash; output column dtypes vs the release; leakage_mass and
    needs_human_review on Rewrite; the console log at DEBUG for any entity value. Always state the model pool
    and provider you used
    — results differ by model. Rewrite with nemotron-3-super fails structured outputs on
    both the branch and 0.4.0; use the default pool for Rewrite comparisons.

What matters now is correctness and discrepancies with main / the released package: wrong outputs,
crashes, dtype/NaN/row-handling defects, PII or exception content leaking into logs/traces. Architecture,
naming, duplication, size and style comments are not useful yet — an active minimization effort owns them
and they will be parked (not ignored) — see the triage replies in the footer.

File findings as comments on this PR — one finding per comment, tagged [breaks-compat],
[wrong-output], [crash], [privacy], [docs-gap], or [sdk-semantics] (phase 3), with: the exact SHA you reviewed
against, the topic area (route/surface), smallest repro (provider-free if possible), and expected vs actual
(one line each).
All coordination for this branch happens on this PR; there is no separate guide or issue form.

Status, known-open, and accepted behavior deltas

Branch role (maintainer, 2026-10-21): this branch is the long-lived base of the graph-SDK feature line. It is not
scheduled to merge into main; later work, including PR2, lands here as reviewed PRs, and main is merged in as its
PRs land. Dogfood it as a moving base: the pin in the dogfooder block is the current head, and the known-open and
accepted-delta lists below describe that head.

Live validation (2026-10-19, operator dogfood vs released 0.4.0 at 5473e8ef; first run 2026-10-16 at
0842f5c5 reported the same outcome): detection+Redact, Substitute and Rewrite run end to end on two live
provider pools via run and preview; outputs match the release on columns, dtypes (under aligned
dependencies; pandas 3 differences are the accepted deltas below), entity coverage, replacement-text leakage
and log hygiene. Timing within 0.7–1.5× of the release across routes; the Substitute replacement-map
serialization fixed at e309c617 holds (10 requests in flight). Evidence retained in the campaign's records.

Size (maintainer decision, 2026-10-21): the original targets were +10K, then +5K, non-test Python lines over the
0.4.0 baseline 30b6d82a. Measured at pin 188ef369: +32,760 non-test (+31,642 excluding tools/) and
+57,383 test. The targets were revised rather than met because the branch was reduced in stages on top of the
reviewed implementation; a rebuild was ruled out. Continuing reduction and simplification will be done in commits
directly to this branch or in PRs made to it directly (the first three, caller-thread execution for every synchronous
body, an eager anonymizer.graph facade and a DataDesigner version range, have landed), on branches named
<author>/graph-sdk-<name>. Size, naming and layering comments stay parked until that work lands (see triage replies).

Known-open — don't file these:

  • Evaluation cutover, native host execution/admission, and Session/identity/matching are
    deferred to PR2+ (maintainer scope ruling, 2026-10-16). evaluate() ships on its existing
    legacy path — behavior-preserving; don't file "evaluation isn't graph-owned" findings.
  • PR1's remaining work is simplification: the implementation is being substantially reduced and
    restructured. Internal modules, names, and layering will churn heavily — architecture/naming
    feedback stays parked (see triage replies).
  • Detection batch speed matches the release with real endpoints (1.00–1.02× at 100–200 rows; the earlier
    internal ~2.1× measurement came from a quadratic scan in the final-output assembly, fixed in 0cc24a4a).
    About 7–8 ms/row of CPU orchestration remains on top of model time. Don't file timing findings under
    1.1× of the release; do file anything above it with row count and model pool.
  • The anonymizer.graph SDK surface is not a contract in this PR: its names, export order, signatures,
    defaults, exception constructors, pickles and import cost may change until the SDK is published under the
    RFC's publication gate (REQ 13). Tests that pin them are refactor safety nets, re-recorded with the change
    that moves them. Continuity, adaptive retrieval, decision waits, state revisions and absence queries have
    moved to PR2+. A changed or removed graph export is not a finding; the shipped Anonymizer/config API and
    the documented result columns are the stable target.
  • Running two Anonymizer processes at once prints OSError: [Errno 98] Address already in use from
    DataDesigner's metrics server, then "OpenTelemetry metrics are unavailable; continuing without them."
    Harmless.
  • Substitute can leave an entity's original text in place when the replacement generator omits it from
    its map; the trace's skipped_span_count shows it. Pre-existing in 0.4.0, model-dependent (seen with
    nemotron-3-super, not with the default pool). Tracked for PR2; still report it with your model pool so
    we see the frequency.

Accepted behavior deltas (a released-vs-branch diff covered by a row here is not a finding):

Delta Routes affected Accepting checkpoint SHA
Rewrite trace: internal _needs_repair / _rewrite_replacement_ready columns are bool (release: object, NaN on no-entity rows); on the staged path _needs_repair is False for withheld rows (release True), since the withheld text is not repaired Rewrite, trace_dataframe only 8f5a9b74, d34ccdfb
Redact: an empty or statically empty format_template (e.g. "" or "{label:.0}") raises a configuration error, and a label template that renders empty during replacement raises instead of silently keeping the original entity text (0.4.0 accepted these and could leave the entity in place). Rationale: fail-closed — an empty template would publish the original PII. Migration: supply a non-empty template (default [REDACTED_{label}]) Replace (Redact) 0c1b719a
Rewrite trace: QA columns (_privacy_qa_reanswer, _quality_qa_reanswer, _quality_qa_compare) hold empty structures instead of NaN on rows with no entities; a batch where no row has entities adds these 4 trace columns (release: absent) Rewrite, trace_dataframe only (both schedules, run/preview, evaluate) b2c9c7bf
pandas 3 only: evaluate() on a Rewrite preview returns the trace _anonymizer_record_id column as str dtype instead of object evaluate, trace_dataframe only b2c9c7bf
A caller-supplied detector that returns duplicated rows, with a preview num_records smaller than the returned entity rows: the branch returns the requested n rows and drops the extra row (failed_records empty); 0.4.0 and main crash with a raw pandas ValueError in _join_new_columns Rewrite route with caller-supplied detectors, preview (Redact/Substitute preview behave the same on both) b2c9c7bf
evaluate() on a Substitute/Redact result whose caller-supplied detector duplicated rows returns one row per result row (7 in the duplicated-detector case); 0.4.0 and main return 9 because their replace-judge merge multiplies the duplicated rows evaluate after Replace with caller-supplied detectors 3c7c7ccc
Rewrite, evaluate() and result.rewrite_config use the privacy goal as admitted (0.4.0 used the goal object as mutated by the detection stage) Rewrite, evaluate a67ef6bd
A standalone combined-finalizer row whose repair decision is missing, null or non-boolean fails that record (EffectRejected, surfaced as a failed record) instead of 0.4.0's silent projection of the first evaluation state; unreachable through built-in routes, only via caller run_workflow overrides or hand-built rows Rewrite combined schedule with caller run_workflow overrides 1301c619
Context variables set inside caller-supplied code (detectors, injected runners, build_graph, adapter run_workflow overrides) are not visible to the caller after run/preview/evaluate return; values set before the call are visible inside (0.4.0 ran caller code in the caller's live context, so writes leaked out). Rationale: every public entry runs in a copy of the caller's context, copy-in and no copy-out, the only semantics an async entry can also offer. Migration: pass results back through return values or your own objects, not through context variables All routes with caller-supplied code 2fc6d810

What has landed

The 15 commits (history squashed 2026-10-20; tree unchanged)
Commit What it adds
a75b1dee Graph values, workflows and static composition; the anonymizer.graph facade
404f64fa Activation, data graphs and bounded plan preparation
6c14a4fa Typed requests, context binding and observations
729a577c Initial bindings, blocking providers and foreign transport
849a203a Execution of admitted graphs under a bounded runtime
d684c273 Evidence qualification against execution accounting
bceffa9d Inspection and extensions; public SDK guide
3de54364 DataDesigner bridge to graph requests
e624f10d Public facade compatibility tests, before the route cutover
0b51cfef Detection and Redact under graph ownership (route cutover, part 1)
ced49332 Rewrite under graph control; facade routing (part 2)
1fdb2937 Substitute generation under graph control (part 3; suite green from the next commit)
372bbd0b Logging: keep sensitive payloads out of library diagnostics
60959fa7 Cutover inventory and preview-branch rules
4bded5e6 Branch-vs-release dogfood kit

Current numbers

Measure Value
Python growth vs baseline 30b6d82a +32,760 non-test / +57,383 test (net, *.py, at 188ef369)
Full suite 5,661 passed at 3a208200 (pandas 2 and 3); parity corpus vs 0.4.0: 0 blocking
CI Run 39620094519 at 188ef369
Wheel at 2fc6d810: one identical wheel on Python 3.11–3.13; compat 265 from the installed wheel
Live dogfood vs 0.4.0 see Live validation in the status block
Substitute benchmark zero-latency mock (1,025 rows): median 0.909×; 250 ms-latency mock, 16 rows: map stage 0.23 s (0.4.0: 0.23 s)

Remaining work

  1. PRs into this branch (branch <author>/graph-sdk/<type>/<name>, the naming order proposed in docs: allow topic prefixes in branch names for long-lived feature lines #312). Landed: refactor(graph): make anonymizer.graph an eager facade #314 (eager anonymizer.graph facade), build: accept data-designer >=0.9.4,<0.10 with capability-checked hooks #315 (DataDesigner >=0.9.4,<0.10), fix(graph): run every Anonymizer body on the caller's thread #316 (caller code and builtin DataDesigner waits on the caller's thread), test(graph): accept CPython patch-release differences in graph SDK tests #318 (tests pass on every supported Python 3.13 patch release).
  2. Main: merged in by merge commit (first at 188ef369). Later main PRs come in the same way as they land, with a port PR when they touch graph-owned code.
  3. Size: see the size paragraph in the status block.
  4. Deferred to PR2: async entry point, a concurrency statement, sharding primitives; residual logging conformance (dependency loggers, exporters, telemetry).

Verification

Each PR into this branch gets the full suite (pandas 2 and 3), repository checks and independent review.

Related Issue and Plan

Maintainer-directed Graph SDK implementation; no linked issue required. See the public SDK guide, cutover inventory and provider-free example.

Type of Change

  • New feature
  • Refactoring
  • Documentation update

Contributor Checklist

  • Conventional Commit title and maintainer-owned no-issue reason
  • Public guide, example and cutover inventory linked
  • Final reviews before un-draft (public API and bundled skill impact, fixture/artifact privacy, conformance and identical-wheel qualification, minimization closeout): see Remaining work

Triage replies (paste verbatim)

Parked:

Parked, not ignored: this is architecture/naming/size/style feedback, which the minimization
effort owns right now. It goes on the parked list and will be revisited after minimization
completes. Thanks for flagging it.

Needs repro:

Needs a repro before it can enter the queue: please add the exact SHA you ran and the smallest
script/config/data that reproduces it (provider-free if possible), with expected vs actual, one
line each. Re-label or comment once it's attached and we'll triage.
SDK semantics:

Logged as [sdk-semantics] input to the public-surface decision; not a PR1 blocker and not parked.
If it is also a wrong output or a crash, re-tag it so it enters the correctness queue.

Model-dependent:

Thanks — reproduced as model-dependent: it occurs with and not with the default pool in our
runs. Logged on the PR2 backlog with your model config attached; not a PR1 blocker. If you see it with
the default pool, please re-flag.

Copy link
Copy Markdown
Collaborator Author

/nvskills-ci

binaryaaron and others added 15 commits October 20, 2026 21:28
Add the engine-side graph values and workflow modules (identity and reference values, workflow declarations, static and dynamic admission and substitution, composition and the metered compiler budget) together with the closed effect and artifact-shape values used by later layers. Introduce the lazy public anonymizer.graph facade over them. Tests cover static workflow analysis and the compiler reference, with the workflow_static corpus.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add the engine-side activation modules (activation values, topology and reducer), data-graph declarations and validation, canonical records, capability declarations and bounded plan preparation. Tests and references cover activation, data graphs and the compiler.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add request policies, values and receipts with a pure reducer, typed context and its admission, resource leases and cleanup facts, ingress envelopes and bounded content-free observations. Add the detection and Rewrite artifact types that requests carry.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…nsport

Add execution declarations and sealed results, bounded blocking-worker adapters, a single physical request driver, callback-ingress inspection, foreign transport entry with a client lease, and asynchronous initial context binding.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add execution admission, the invocation runtime and scheduling, dispatch, delegated slot borrowing, frames, map and context materialization, publication and provenance, cleanup, entry points and the executor, plus the NDD source frame. Tests cover effects, map, delegated, foreign and optional-tail conformance with their references.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add evidence admission and authentication, provenance reconciliation and release qualification, with the qualification and evidence suites, witnesses and the evidence model.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
… SDK

Add request inspection and extensions (context providers and retained-result views) and lazy root-package exports including graph. Document the public graph SDK in docs/concepts/graph-sdk.md with mkdocs navigation, reference pages and a provider-free example, and add the public API and export-surface tests.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
NddAdapter.run_workflow gains optional source_frame, request_bridge and rewrite_binding parameters; the change is additive apart from removing the private _detect_missing_records. Add the version-qualified DataDesigner request entry, row partitioning and source positions, stage binding, the Rewrite evaluation binding, the data-designer==0.9.4 pin with a drift test, and the compat pytest marker.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add test_production_contracts.py, a provider-free compatibility oracle for the public facade covering Redact projection and failure, saved-result evaluation and DataDesigner retry. It lands before any route moves so each cutover is measured against it.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Admitted core, optional-latent and final stages replace the builtin detection controller. Add the whole-frame detection and Redact consumers, the shared harness (_opaque_redact, _frame_publication, harness retention), the NDD route module _route.py and the graph SDK _authoring_defaults.py. Redact evidence checking rejects empty templates, with matching config, replace.md and skill updates and the skill signature files. Validation-plugin and local-transform mediation and the frozen detection corpora are included. This commit also carries the logging hunks of replace/replace_runner.py.

Part of the B10–B12 route cutover; the full suite is green again at B12.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…acade

Graph control owns Rewrite transitions for both DataDesigner schedules. The legacy repair loop and CombinedRewriteWorkflow.run are retired, and one source-bound invocation runs detection plus the semantic tail. The public Anonymizer facade is routed here, including the logging hunks of interface/anonymizer.py. Evaluation source-position fixes are included; evaluate() keeps its existing path.

Part of the B10–B12 route cutover; the full suite is green again at B12.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Move Substitute map selection, generation and acceptance under graph control, with native map requests, compatibility entries and the Substitute lineage and compat suites. This closes the route cutover; the full suite is green from here.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Keep sensitive payloads out of library log output. This covers the logging change except interface/anonymizer.py and replace/replace_runner.py, whose logging hunks ride B11 and B10.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add docs/development/graph-sdk-cutover.md and the AGENTS.md section describing the graph SDK preview branch rules.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add tools/dogfood/ with a README, compare.py, dogfood_run.py and model and provider configs for comparing this branch against the released package.

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@binaryaaron
binaryaaron force-pushed the codex/anonymizer-graph-sdk-contracts branch from 2fc6d81 to 4bded5e Compare October 21, 2026 01:22
binaryaaron and others added 12 commits October 21, 2026 01:23
Replace the lazy attribute table with static imports and a sorted
__all__ that binds the same names to the same objects. This drops the
import_module/TYPE_CHECKING mirror of the export list.

The pure-import test no longer asserts that importing anonymizer.graph
avoids pandas; it keeps its two engine-component cases. The export pin is
re-recorded for the new sorted __all__ order.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Importing anonymizer.graph now pulls in the engine eagerly. Defer the
four edges that dragged in pandas and numpy: the harness frame types
become TYPE_CHECKING imports with local imports at their few runtime
uses, and rewrite artifacts import numpy and pandas inside the one
method that needs them.

import anonymizer.graph drops from roughly 580 ms and 735 modules to
roughly 290 ms and 280 modules, with pandas no longer loaded.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
…equest bridge

Restore the upper-bounded DataDesigner range used on main instead of the
exact 0.9.4 pin. The graph request bridge no longer compares the installed
version; it checks the private DataDesigner hooks it relies on:

- at bind time, before any work runs: DataDesigner._create_resource_provider,
  ModelRegistry's model_facade_factory parameter and ModelFacade.agenerate;
- when DataDesigner first creates a provider or facade, before any request:
  the registry's facade factory and the facade client's HTTP fields, which
  are instance attributes and cannot be checked earlier.

A missing hook raises AnonymizerWorkflowError naming the attribute, the
supported range and the installed version. The bridge records the failure
and re-raises it when the binding exits, so DataDesigner's per-row error
handling cannot turn it into dropped records.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
…oks lazily

data-designer 0.9.5 classifies httpx.NetworkError and RemoteProtocolError
(for example a ReadError) as connection errors, so its request executor
now retries them. A bridge that admits one physical attempt per chain
(detection) then saw a second attempt and rejected the whole invocation,
where 0.9.4 kept the record's outcome unknown. For single-attempt chains
the bridge now hands DataDesigner those errors as a
SingleAttemptTransportError, which every supported release classifies as
a non-retryable API error; errors 0.9.4 already retried (ConnectError,
timeouts) are passed through unchanged.

Private DataDesigner names are no longer imported at module scope, so
importing anonymizer does not depend on them:

- create_retry_transport and ParserException are resolved lazily and
  checked when the bridge binds;
- the health-check skip switch and AsyncTaskScheduler's
  salvage_max_rounds, used only by the Substitute native map, are checked
  when the map plans its requests, before any work runs;
- ensure_async_engine_loop is imported on first use.

Each missing hook raises the standard capability error.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Every route runs DataDesigner through NddAdapter.run_workflow, which binds
the source frame to DataDesigner's private dataset builder. A release
without those hooks failed with a bare AttributeError message.

The binding now checks, before any work runs,
DataDesigner._create_dataset_builder and the artifact-storage module's
BatchStage members and SDG_CONFIG_FILENAME. When DataDesigner creates the
builder, before it runs, it checks the builder's
_initialize_generators_and_graph, _write_builder_config and
_data_designer_config, and the storage methods and paths it wraps. Each
failure raises the standard capability error naming the attribute, the
supported range and the installed version.

A real DataDesigner always requires the hooks; a caller's stand-in
designer without a dataset builder is still left unbound.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Builtin Redact, Annotate and Hash, and builtin detection or Substitute
map generation reached from a caller override through super(), ran
their DataDesigner calls on a library worker thread. In 0.4.0 they ran
on the thread that called run() or preview(). Hand those bodies to the
caller's thread like every other body, and run a handoff issued on the
serving thread inline instead of raising.

New placement tests pin 0.4.0 behaviour for builtin Redact, Annotate
and Hash, for detectors that delegate to the builtin run(), and for
Substitute under a caller replace runner, from the main thread, a
non-main thread and inside a running event loop.

The route-resolution rows keep pinning which route each runner shape
takes and now pin that every route hands its body to the caller. The
interrupt-drain test delivers a real SIGINT instead of simulating one
in the kernel's completion wait, which the caller no longer reaches
while a body runs.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
…r pool

Every synchronous body already returned to the caller's thread, but the
kernel still started a one-worker pool per invocation whose thread only
forwarded the body back to the caller, and the caller polled its queue
every 10 ms. Submit delegated bodies straight to the caller's queue
through a caller-thread submitter, wake the caller with a completion
marker instead of the poll, and drop the per-invocation pool and the
caller_callbacks switch, which every entry now sets.

Standalone use outside a facade invocation keeps the worker submitter
and BlockingPool. A Ctrl-C that lands after a body's result was set no
longer turns into an InvalidStateError.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
The patch replaced DataDesigner's scheduler wait process-wide so that a
graph cancellation reached a DataDesigner run waiting on a library
worker thread. Every DataDesigner wait now runs on the thread that
called the public entry point, where Ctrl-C reaches DataDesigner's own
handler as in 0.4.0, so the patch is no longer needed.

Extend the subprocess Ctrl-C test to builtin Redact, Redact under a
caller detector that delegates to the builtin run(), and Substitute
under such a detector.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
run(), preview() and evaluate() run every synchronous body, builtin or
supplied, on the thread that called them, at every depth, with a copy
of the caller's context variables. Say so in the graph SDK concepts
page and the cutover notes, correct the claim that callbacks get the
caller's context rather than a copy, and state where a builtin reached
through super() runs.

Builtin detection now always receives the caller handoff, so its
inline fallback is gone.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
A Ctrl-C that landed after the caller took a body from its queue, or
just before it settled the body's future, left that future unresolved.
The host then waited for it forever and the drain loop absorbed every
further Ctrl-C, so the process only ended on SIGKILL. Track every queued
body until it settles and, when an interrupt leaves serving, settle any
that remain as failed before re-raising.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Caller code and every DataDesigner wait run on the caller's thread, but
the per-invocation event-loop thread still runs internal bookkeeping and
DataDesigner's engine threads still run Anonymizer column generators,
as on main. Say so instead of claiming the loop thread runs no
Anonymizer code, and fix a comment that still said a nested handoff
raises.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
When a second Ctrl-C landed while the drain loop was serving, the
interrupt handler asked the host loop to cancel even after that loop had
closed, and the resulting RuntimeError replaced the KeyboardInterrupt.
Share one guarded stop request between the handler and the drain loop.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
binaryaaron and others added 5 commits October 21, 2026 04:12
A second Ctrl-C landing inside the interrupt handler, before it settled
the waiting bodies, could still leave one unsettled and the drain loop
waiting forever. Settle first, then request the stop, and settle again
on every drain pass so no interrupt window skips it.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
After an interrupt settles every waiting body, the drain loop could
still take one of them from the queue and set its result again, which
raised InvalidStateError in place of the KeyboardInterrupt. Skip bodies
that are already settled, and test the case directly.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Until the SDK is published, anonymizer.graph and the engine modules
behind it may change names, export order, signatures, defaults,
exception constructors, pickles and import cost. The tests that pin
them are refactor safety nets, re-recorded with the change they cover.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Settled bodies resolve as failures rather than raising, so the test's
operation can finish before the stop request reaches the host. The
interrupt path then reads the result's execution, which the bare list
the operation returned does not have, and the AttributeError replaced
the KeyboardInterrupt. Return a result shaped like every production
operation's.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Brings in the quality and throughput tuning guide (#261). The only
conflict was the mkdocs nav; both entries are kept: Graph SDK under
Concepts, and the tuning guide at the top level, as on main.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
@binaryaaron

Copy link
Copy Markdown
Collaborator Author

/nvskills-ci

svc-nvskills-signing and others added 2 commits October 21, 2026 15:10
Signed-off-by: nvskills-svc-account <svc-nvskills-signing@nvidia.com>
…#318)

dataclasses.replace() now raises TypeError instead of ValueError for a
missing InitVar, so test_copies_require_the_owner_key accepts either
type while still asserting the message. The pinned export facts recorded
the enum-class signature, which newer 3.12 and 3.13 patch releases report
as (*values); enum classes now record no signature, as exception classes
already do, and the three recorded entries were updated to match.

🤖 Generated with Claude Code

Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Co-authored-by: Claude <noreply@anthropic.com>
@binaryaaron
binaryaaron deleted the codex/anonymizer-graph-sdk-contracts branch October 21, 2026 16:57
@binaryaaron
binaryaaron restored the codex/anonymizer-graph-sdk-contracts branch October 21, 2026 17:39
@binaryaaron binaryaaron reopened this Oct 21, 2026
@binaryaaron
binaryaaron deleted the codex/anonymizer-graph-sdk-contracts branch October 21, 2026 17:41
@binaryaaron

Copy link
Copy Markdown
Collaborator Author

This PR was closed automatically when its branch was renamed to aagonzales/graph-sdk/feat/main. The same branch, history and description continue as #319. Please read, dogfood and comment there.

binaryaaron added a commit that referenced this pull request Oct 21, 2026
The line branch was renamed from codex/anonymizer-graph-sdk-contracts to
aagonzales/graph-sdk/feat/main, which closed PR #302; draft PR #319 now
carries the branch. AGENTS.md names the new branch and both files link
PR #319, noting that it was formerly #302.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
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.

2 participants