feat: persist scheduler events to JSONL#800
Conversation
Add an opt-in RunConfig flag that writes create-run scheduler events beside dataset artifacts. Keep preview and default runs disk-free, and make file I/O failures non-fatal. Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>
|
Fern preview: https://nvidia-preview-pr-800.docs.buildwithfern.com/nemo/datadesigner
|
Greptile SummaryThis PR adds an opt-in
|
| Filename | Overview |
|---|---|
| packages/data-designer-engine/src/data_designer/engine/observability.py | Adds JsonlSchedulerEventSink: context manager that opens the file in append mode, writes/flushes each event, self-disables on first write failure (re-raises once for caller logging), and closes cleanly. |
| packages/data-designer-engine/src/data_designer/engine/dataset_builders/dataset_builder.py | Wraps the async-run block in the event-sink context when write_scheduler_events is enabled; preview path unchanged (no sink); nullcontext fallback cleanly passes None to _prepare_async_run. |
| packages/data-designer-config/src/data_designer/config/run_config.py | Adds write_scheduler_events: bool = False field with docstring; clean addition with no side effects on existing config. |
| packages/data-designer-engine/tests/engine/test_observability.py | Four new test cases covering: append and flush, open failure, close failure (idempotent), and write-failure disabling. |
| packages/data-designer-engine/tests/engine/dataset_builders/test_dataset_builder.py | Three integration tests covering create (enabled/disabled), preview exclusion, and resume append with distinct run_ids. |
| packages/data-designer-config/tests/config/test_run_config.py | Two simple unit tests confirming default-off and explicit-on behavior. |
| architecture/dataset-builders.md | One-line documentation addition for the scheduler_events.jsonl artifact. |
Reviews (7): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile
Code Review: PR #800 — feat: persist scheduler events to JSONLAuthor: @eric-tramel · Base: SummaryAdds an opt-in FindingsCorrectness — solid, one subtlety worth confirming
Concurrency / Performance
Robustness
Style / Conventions
Test Coverage — strong
Suggestions (priority order)
None of these are blocking; the PR is safe to merge as-is given the opt-in default. Structural Impact (graphify, 2.3s)Risk: HIGH (18 import direction violation(s))
Import Direction Violations (18)Legal direction: interface -> engine -> config
High-Connectivity Changes
Cross-Package Dependencies
Reviewer note on structural impact: The 18 flagged "config → engine" violations are almost certainly false positives from method-name collisions ( VerdictApprove with minor follow-ups. The change is well-designed, correctly layered, non-fatal on I/O errors, and thoroughly tested for its stated behavior. The failure-handling flow (disable-and-warn-once) is correct end-to-end across the sink and scheduler. Recommended non-blocking follow-ups: |
nabinchha
left a comment
There was a problem hiding this comment.
Thanks for putting this together, @eric-tramel — clean, well-scoped feature with genuinely thorough tests.
Summary
Adds an opt-in RunConfig.write_scheduler_events flag that persists the existing structured scheduler event stream to <dataset root>/scheduler_events.jsonl for create runs, appending a new segment on resume while keeping the default and preview paths disk-free. The implementation matches the stated intent in the PR description, and the failure handling (open/write/flush/close treated as non-fatal) is well covered by tests.
Findings
Warnings — Worth addressing
packages/data-designer-engine/src/data_designer/engine/observability.py:239 — Docstring promises more than the sink enforces
- What: The class docstring says events are appended "without affecting generation on file errors," but
emit_scheduler_eventactually re-raises on write/flush failure. The non-fatal guarantee is really provided byAsyncTaskScheduler._emit_scheduler_event, which wraps the call intry/exceptand logs a single warning. - Why: The error-handling contract is inconsistent between the docstring and the implementation, and the "non-fatal" guarantee leaks to the caller. The sink implements the general
SchedulerAdmissionEventSinkprotocol, so a future caller using it directly — reasonably trusting the docstring — would get an exception on the first write error and could crash generation. - Suggestion: Either self-contain the guarantee (log a single warning + disable internally and
returninstead ofraise), or update the docstring/emit_scheduler_eventto state explicitly that it disables itself and re-raises, so callers know they must handle it. The current split works for today's single caller; it's the contract clarity that's worth tightening.
architecture/dataset-builders.md:89 — Doc now understates observability persistence
- What: That line states per-run observability (task traces, model-usage logs, telemetry events) "is scoped to the current invocation — the original run's in-memory state is not persisted across process boundaries." This PR makes a category of per-run scheduler observability persistable to disk and appended across resume segments.
- Why: Contributors rely on that architecture note to reason about what does and doesn't survive a run. Leaving it as-is means the recorded architecture is now incomplete relative to the new behavior.
- Suggestion: Add a sentence noting the opt-in
scheduler_events.jsonlartifact (in this PR, or an explicit follow-up issue) so the doc stays aligned with the code.
Suggestions — Take it or leave it
packages/data-designer-engine/src/data_designer/engine/observability.py:239 — Single-writer assumption is implicit
- What:
write,flush, and theself._file = Nonereset aren't synchronized. This is safe today because scheduler events are only emitted from the single background asyncio loop thread, so writes are serialized. - Why: The class is otherwise a general-purpose sink; if it's ever wired to a multi-threaded emitter later, interleaved writes/flush could corrupt lines or race on the disable path.
- Suggestion: A one-line comment documenting the single-writer expectation would save a future reader (or refactorer) from having to re-derive it.
What Looks Good
- Clean opt-in surface —
contextlib.nullcontext()for the disabled path keeps the hot path allocation-free and the default zero-I/O; preview stays memory-only by construction. - Genuinely thorough tests — config defaults, unicode round-trip, append semantics, and every failure mode (open/close/write) are covered, plus create/preview/resume at the builder level with the nice
run_idsegment-differentiation assertion on resume. - Failure handling avoids warning storms — disabling the sink after the first write failure (and the idempotent, warn-once
close()) is a thoughtful touch, and the test that assertswrite/closeare each called exactly once nails it down. - Docstring calls out the real trade-offs — flagging potential sensitivity of the labels and the per-event flush overhead in the
RunConfigdocs is exactly the right place for operators to see it.
Verdict
Needs changes — two light Warnings worth resolving before merge:
- Reconcile the sink's docstring with its re-raising behavior (or make the non-fatal guarantee self-contained).
- Update
architecture/dataset-builders.md(or file a follow-up) to reflect the new opt-in on-disk scheduler-event artifact.
Both are quick; the core implementation is solid and ruff check / ruff format --check are clean on the changed files.
This review was generated by an AI assistant.
|
@nabinchha Thanks for the review — addressed all three points in
Focused validation: 9 observability tests passed, plus Ruff check and format verification. I’ve re-requested your review. |
nabinchha
left a comment
There was a problem hiding this comment.
Thanks @eric-tramel — both warnings are addressed: the docstring now documents the single-writer + re-raise-once contract, and the architecture doc notes the opt-in scheduler_events.jsonl artifact. ruff check/format are clean on the changed files. Ship it.
…s-sink # Conflicts: # packages/data-designer-config/src/data_designer/config/run_config.py
📋 Summary
Adds an opt-in
RunConfig.write_scheduler_eventssetting that persists the existing structured scheduler event stream beside dataset artifacts. This gives operators a durable JSONL timeline for create runs while preserving the current zero-I/O default and keeping preview memory-only.The sink appends and flushes each event, closes with the scheduler lifecycle, and treats open, write, flush, and close failures as non-fatal diagnostics failures.
Perfetto visualization derived from the opt-in scheduler JSONL for an 8-row gpt-5.5 workflow with 24 successful model calls. The scheduler saturates its eight-task concurrency cap, briefly queues work, and cleanly drains all workers and resources.
🔗 Related Issue
Related to #727. This PR exposes the raw opt-in scheduler event stream; it does not implement the broader automatic coarse diagnostics contract.
🔄 Changes
RunConfig.write_scheduler_events=False.<dataset root>/scheduler_events.jsonl.Usage
The file is raw JSON Lines and may include column, provider, model, task, and resource labels. It is not direct Perfetto input.
Changelog
🧪 Testing
Ran
make check-all-fixsuccessfully. Ran the fullmake testsuite with 3,797 tests passing and 1 skipped. After review-driven failure-handling changes, reran all 2,173 engine tests successfully.make testpasses✅ Checklist
RunConfig)