Skip to content

feat(engine): #422 slice 1 — typed event vocabulary + EngineSink inversion for agent_stream - #423

Merged
justrach merged 6 commits into
mainfrom
feat/422-engine-slice1
Aug 6, 2026
Merged

justrach merged 6 commits into
mainfrom
feat/422-engine-slice1

Conversation

@justrach

@justrach justrach commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Slice 1 of #422: the engine stops writing to the terminal and starts emitting typed events through a strict sink interface. First offender converted: agent_stream.zig now has zero imports of term.zig/agent_render.zig/ansi.zig/anim.zig.

What's in it

  • engine_events.zig — the new internal vocabulary: one tagged union covering every emission agent_stream produces (stream_begin, reasoning_delta, text_delta, thinking_fold_toggle, stream_aborted{interrupted|stalled|dropped}, stream_complete, stream_finished) plus Cursor{generation, sequence}.
  • protocol_seq.writeEventStamped — cursor stamping integrates with the existing seq counter (durable events reserve ids inside the stdout lock; pulses observe) so the --json wire's gap-free numbering and bytes are unchanged. One counter, no duplication.
  • engine_sink.zig — strict EngineSink vtable (sinks receive events only). TuiSink reproduces every old inline TTY branch gate-for-gate via agent_stream_render.zig (spinner + live Thinking block moved verbatim); JsonSink emits the existing wire lines byte-identically, pinned by test.
  • agent_output.zigsay/sayApiError/emit moved out of agent.zig (now 538/600) with member aliases so call sites are untouched.

Evidence it changes nothing

A before/after eval harness (goldens captured from v0.0.239 main, determinism proven by double-run) gates the change:

  • --json protocol: scripted session against a local mock Codex backend (tool call + two text turns + mode/effort controls) — full 22-event NDJSON stream, stderr, exit code, and backend request count byte-identical to baseline.
  • Interactive TUI: the same conversation on a real 40x120 PTY — 79-line normalized transcript byte-identical to baseline.
  • zig build test: 981 → 989, delta exactly the 8 new unit tests (engine_events ×3, engine_sink ×4, protocol_seq ×1), all wired through test_hooks.zig; none removed. zig fmt --check clean.

Known slice-1 debt (documented in-code, on the epic's work list)

  • agent_ws.zig still reaches spinner state via Agent aliases; agent_argstream.emitArgText still branches json/tty inline — next offenders.
  • TuiSink wraps *Agent as its render-state handle until the render state moves out.
  • Cursor.bumpGeneration exists but nothing calls it yet — lands with the serve/attach slice (Local daemon: graff agents / graff attach — sessions that survive terminal close #420).
  • Per-stream md/thinking resets stay inline deliberately: they ran unconditionally in all modes before, and behavior preservation beats purity in this slice.

Part of #422.

justrach and others added 6 commits August 6, 2026 10:57
…d ids (#422)

Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
…tion, sequence} cursors (#422)

Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
…ine cap, #422)

Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
…der.zig (#422)

Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
…only (#422)

Co-Authored-By: Codegraff <blackfloofie@codegraff.com>
…state the real slice-1 debt (#422)

Co-Authored-By: Codegraff <blackfloofie@codegraff.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.

1 participant