Follow-on to scheduler-sim-repl-verbosity: collapses the REPL's per-event "[sim] t=... session=..." preamble to print once per distinct rendered (timestamp, session) run of consecutive echoed events, instead of once per event, in both step mode and continue mode's structural-event echo. Requirement 1.3 deliberately keys the dedup on the rendered, second-granularity timestamp rather than the raw nanosecond clock value, which ticks on every event and would otherwise never repeat -- confirmed against a real user-supplied transcript before writing requirements (see research.md). Requirement 2 preserves event selection, ordering, content, every other REPL command, and all scheduling decisions/state exactly as scheduler-sim-repl-verbosity's Requirement 5 already established. Generated via kiro-spec-quick (single-pass, no approval gates) with an inline requirements review gate, an inline design review gate, and an independent task-graph sanity review (one NEEDS_FIXES round, repaired: added file-size watch bullets to tasks 1.1/1.2 per design.md's own housekeeping directives). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YAWMhfRJFnmmnG8h97yUDd
15 KiB
Design Document
Overview
repl_print_event_summary() (repl_break.c) prints a "[sim] t=<ns> (<ISO8601>) session=<N>" preamble line unconditionally before every echoed event's own content line — this is its only call site, xce_repl_on_event(), which already gates WHICH events reach it per scheduler-sim-repl-verbosity's Event Echo Gate (structural events always; routine events only in step mode). Because the preamble is a fixed prefix printed on every call regardless of what the previous call printed, an operator watching several consecutive events that land in the same displayed second and session sees that context restated once per event, diluting the actual event content it's meant to introduce.
This feature makes repl_print_event_summary() preamble-aware of its own immediately-preceding output: it prints the preamble only when the rendered (second-granularity) timestamp or the displayed session number has changed since the last time a preamble was actually printed; otherwise it prints only the event's content line. No other REPL behavior changes.
Users: Operators running step or continue (structural-event) transcripts through xce_scheduler --sim <file> --interactive.
Impact: repl_print_event_summary() gains a short-circuit at its top; xce_sched_repl_state_s gains two small cache fields recording what was last actually printed.
Goals
- Print the timestamp/session preamble once per distinct (rendered timestamp, session) run of consecutive echoed events, not once per event.
- Base "distinct" on the rendered, second-granularity timestamp and displayed session number — not the underlying raw nanosecond clock value, which changes on every event and would otherwise defeat deduplication entirely.
- Leave every echoed event's own content line, its ordering, and whether it is echoed at all completely unchanged.
Non-Goals
- Changing the Event Echo Gate's routine/structural classification (
repl_event_is_structural()) — which events reachrepl_print_event_summary()at all is unaffected. - Changing the Round Rollup / collapsing behavior
continuemode already uses for routine events and per-round decision summaries. - Changing decision output (
sim_print_decision/sim_print_decision_gate), which has no preamble of its own and is untouched. - Reformatting the preamble's own content (timestamp precision, session-number convention) — only whether it is printed, not what it looks like when it is.
Boundary Commitments
This Spec Owns
- The preamble-print decision inside
repl_print_event_summary()'s single call site. - Two new cache fields on
xce_sched_repl_state_s(repl_internal.h) recording the last preamble actually printed.
Out of Boundary
- The Event Echo Gate's classification logic (
repl_event_is_structural(),repl_break.c) — unmodified, this spec only changes what happens after an event has already been selected for echoing. - The Round Rollup / collapsing logic (
repl_rollup.c) andsim_print_decision_gate()(sim_loop.c) — both unmodified. - Every REPL command's parsing, dispatch, or output other than this one preamble line.
Allowed Dependencies
xce_sched_repl_state_s(repl_internal.h, existing) — the two new cache fields live here, alongside this file's other REPL-session-scoped state.repl_format_ts_human()(repl_fmt.c, existing, fromscheduler-sim-repl-verbosity) — reused as-is to render the human-readable timestamp string used for the dedup comparison; not modified.
Revalidation Triggers
- If
repl_format_ts_human()'s rendered granularity or format ever changes, re-verify the dedup key still reflects what an operator actually sees as "the same displayed context." - If
repl_print_event_summary()ever gains a second call site, revisit whether a single "last printed" cache still correctly models "immediately preceding echoed event" across both sites.
Architecture
Existing Architecture Analysis
repl_print_event_summary() is static and has exactly one caller, xce_repl_on_event() (repl_break.c), itself reached only when sim->interactive and sim->repl_state != NULL — both already guaranteed by the time this function runs, so no new NULL-handling is introduced. The function currently: (1) unconditionally builds and prints the preamble, then (2) switches on ev->type to print the event's own content. This design touches only step (1); step (2) and the surrounding gate in xce_repl_on_event() are unchanged.
xce_sched_repl_state_s already carries several REPL-session-scoped caches added by prior tasks (the Round Rollup's collapse-state fields, units_mode) — the two new fields here follow that same established pattern: session-lifetime state, zero-initialized by repl_state_ensure()'s existing calloc, with no additional lifecycle code required (an empty string and a 0 session number can never equal a real echoed event's rendered values, so the "no preceding echoed event" case, AC 1.2, is satisfied by zero-initialization alone — the same convention every field in this struct except units_mode already relies on).
Architecture Integration:
- Selected pattern: extend one existing function's own early logic; no new component, no new file.
- Domain/feature boundaries: strictly inside the REPL Output domain (
repl_break.c/repl_internal.h)scheduler-sim-repl-verbosityalready established; no other domain (Round Rollup, Resource Formatter, Help/Breakpoint) is touched. - Existing patterns preserved:
xce_sched_repl_state_sas the single home for REPL-session-scoped display state;repl_format_ts_human()as the sole human-timestamp renderer. - New components rationale: none — this is a localized behavior change to one existing function plus two state fields, not a new component.
- Steering compliance:
src/scheduler/CLAUDE.md's 800-line-per-file cap applies torepl_break.c, which is already at 949 lines (an accepted residual fromscheduler-sim-repl-verbositytask 9.2). This spec's addition is small (a handful of lines inside one existing function), but implementation must re-check the file's line count after landing it and, per this file family's established practice, empirically test extractingrepl_print_event_summary()(and its Event Echo Gate neighbors) into a new file if the total meaningfully exceeds the residual already accepted — not a decision to make speculatively before the actual diff size is known.
File Structure Plan
Modified Files
src/scheduler/src/repl_break.c—repl_print_event_summary()gains a preamble-print decision at its top: render the candidate human timestamp and displayed session number, compare againstrs->last_echo_ts_human/rs->last_echo_session; print the preamble and update both cache fields only when either differs (or the cache is still at its zero-initialized default), otherwise skip straight to the event's own content switch.src/scheduler/src/repl_internal.h—xce_sched_repl_state_sgainslast_echo_ts_human(fixed-size char buffer, same size as the existingrepl_format_ts_human()output buffers elsewhere in this file family) andlast_echo_session(uint64_t), both doc-commented as this feature's cache, zero-initialized by the existingrepl_state_ensure()allocation path with no additional lifecycle code.
New/Modified Test Files
src/scheduler/testsuite/test_repl_event_echo.c(extend, existing home for Event Echo Gate behavior this feature builds directly on top of) — the new preamble-dedup tests (see Testing Strategy). This file is at 705 lines going into this spec; re-check its line count after landing these tests and split into a newtest_repl_step_verbosity.c(following this file family's established "duplicate fixture helpers, don't share" convention — seescheduler-sim-repl-verbositydesign.md's own Corrections for the precedent) if it approaches this project's ~1000-line split trigger, rather than growing past it.
Requirements Traceability
| Requirement | Summary | Components | Interfaces | Flows |
|---|---|---|---|---|
| 1.1 | Omit preamble when rendered timestamp+session unchanged | Event Echo Preamble Deduplicator | repl_print_event_summary() |
n/a |
| 1.2 | Print preamble when rendered timestamp+session changed, or no prior echo | Event Echo Preamble Deduplicator | repl_print_event_summary() |
n/a |
| 1.3 | Dedup key is rendered (second-granularity) timestamp + session, not raw ns | Event Echo Preamble Deduplicator | repl_print_event_summary() |
n/a |
| 1.4 | Event content/ordering/echo-selection unaffected | Event Echo Preamble Deduplicator | repl_print_event_summary() |
n/a |
| 2.1 | No event newly suppressed or added | Out of Boundary (Event Echo Gate unmodified) | n/a | n/a |
| 2.2 | Ordering preserved | Event Echo Preamble Deduplicator | repl_print_event_summary() |
n/a |
| 2.3 | Every other command's behavior unchanged | Out of Boundary | n/a | n/a |
| 2.4 | No change to scheduling decisions/state | Out of Boundary (display-only change) | n/a | n/a |
Components and Interfaces
| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies (P0/P1) | Contracts |
|---|---|---|---|---|---|
| Event Echo Preamble Deduplicator | REPL / repl_break.c | Print the timestamp/session preamble once per distinct rendered context instead of once per event | 1.1-1.4, 2.2 | repl_format_ts_human() (P0), xce_sched_repl_state_s (P0) |
State |
REPL Output (repl_break.c)
Event Echo Preamble Deduplicator
| Field | Detail |
|---|---|
| Intent | Suppress the timestamp/session preamble on an echoed event when it would exactly restate the immediately preceding echoed event's rendered context |
| Requirements | 1.1, 1.2, 1.3, 1.4, 2.2 |
Responsibilities & Constraints
- Computes the candidate human-readable timestamp via the existing
repl_format_ts_human(xce_core_now(&sim->core), ...)call already present inrepl_print_event_summary(), and the candidate displayed session number (rs->session_count + 1, the existing expression already used) — no new value sources. - Compares the candidate pair against
rs->last_echo_ts_human/rs->last_echo_session. Prints the preamble and updates both cache fields when they differ, or when the cache is still at its zero-initialized "never printed" default (empty string / session0, values no real echoed event can legitimately produce). Skips the preamble entirely, with no cache update, when they match (Requirement 1.1). - The comparison is on the RENDERED values only — the underlying raw nanosecond
xce_core_now()return value is never part of the dedup key, since it changes on every event and two events sharing the same displayed second still have different raw values (Requirement 1.3). - The event's own content line (the existing
switch (ev->type)block) is printed unconditionally regardless of the preamble decision — this component only ever suppresses the preamble, never the content (Requirement 1.4, 2.2).
Dependencies
- Inbound:
repl_print_event_summary()(repl_break.c, existing) (P0) - Outbound:
repl_format_ts_human()(repl_fmt.c, existing) (P0)
Contracts: Service [ ] / API [ ] / Event [ ] / Batch [ ] / State [x]
State Management
- State model: two new fields on
xce_sched_repl_state_s—last_echo_ts_human(the last rendered human timestamp actually printed in a preamble) andlast_echo_session(the last displayed session number actually printed). - Persistence & consistency: in-memory, scoped to one REPL session, written only by this component, read only by this component. Zero-initialized by
repl_state_ensure()'s existingcalloc— no dedicated default-assignment step is needed (unlikeunits_mode's documented exception inscheduler-sim-repl-verbosity), because the zero defaults (empty string, session0) can never collide with a real echoed event's rendered values. - Concurrency strategy: none required — single-threaded sim driver, same as every other REPL state field.
Implementation Notes
- Integration: no change to
repl_print_event_summary()'s call site or signature — purely internal to the function body. - Validation: covered entirely by the child-process stdout-capture technique
test_repl_event_echo.calready establishes for Requirement 1 (Event Echo Gate) — no new test harness needed. - Risks:
repl_break.c's existing 949-line accepted residual (see Architecture Integration above) — watch, don't pre-split.
Data Models
Domain Model
This feature adds no new domain concept — it extends the existing "REPL session-scoped display state" already modeled on xce_sched_repl_state_s (repl_internal.h), the same struct the Round Rollup and Resource Value Formatter components added fields to in scheduler-sim-repl-verbosity.
Logical Data Model
| Field | Type | Purpose |
|---|---|---|
last_echo_ts_human |
char[32] |
The human-readable timestamp last actually printed in an event's preamble (same buffer size repl_format_ts_human()'s other call sites already use) |
last_echo_session |
uint64_t |
The displayed session number last actually printed in an event's preamble |
Consistency & Integrity: both fields reset together only at REPL session start, via repl_state_ensure()'s existing calloc zero-initialization — no explicit reset code path exists or is needed, since no other event in this struct's lifecycle should cause "what was last displayed" to become stale mid-session.
Error Handling
Error Strategy
No new failure modes: this feature performs only in-memory string/integer comparisons and a fprintf that already exists at this call site — no allocation, no I/O beyond the pre-existing stdout write.
Monitoring
No new logging — the existing [sim] ... stdout convention is the only output surface, and this feature only changes how often one of its existing lines appears.
Testing Strategy
Integration Tests (extend test_repl_event_echo.c, child-process output capture — see File Structure Plan for the split-if-needed note)
- Two consecutive echoed events with identical rendered timestamp and session: the second event's preamble line is absent, its content line still appears (1.1, 1.4).
- Two consecutive echoed events whose session differs (a session boundary occurs between them): both events print their own full preamble (1.2).
- Two consecutive echoed events whose raw high-resolution timestamps differ but whose rendered (rounded) timestamp and session are identical: the second event's preamble is still omitted — the critical case distinguishing this feature's actual dedup key from a naive raw-timestamp comparison (1.3).
- The very first echoed event in a fresh REPL session prints its preamble (no prior echo exists to compare against) (1.2).
- A structural event echoed under
continuemode after a stretch of suppressed routine events: its preamble prints or is suppressed based on the same rendered-context rule, exercising the dedup logic outsidestepmode too (1.1, 1.2). stepmode's full event/content ordering and count are unchanged versus this feature's baseline — regression check alongside this file's existing Event Echo Gate assertions (2.1, 2.2).
Regression (existing suites, re-run, no new tests)
test_repl_execution_control.candtest_repl_equivalence.c: confirm no other REPL command's output changed and the REPL/batch decision-equivalence guarantee still holds (2.3, 2.4) — the same spot-check disciplinescheduler-sim-repl-verbosity's task 10.1/10.2 already established for this exact concern.