You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
graff has six-plus frontends today — interactive REPL/TUI (mainloop + zigzag graff repl), one-shot -p, the --json protocol, graff serve (embedder), the Tauri GUI, and iOS — but no formal engine boundary. The core rule this issue proposes:
Build one headless engine, preserve the CLI as the behavioral contract, use the REPL to exercise it interactively, and build the TUI/GUI as stateful clients rather than reimplementations.
Observed symptoms of the missing boundary (all from live sessions this week)
Core prints to a frontend it doesn't know: say() calls inside turn/tool logic render in the REPL but silently vanish in -p (the auto-promote notice is invisible in one-shot runs; only the trace note proves it fired). Structured results decided per-adapter would make every surface consistent.
The GUI reimplements the runtime: gui/src-tauri/src/runtime/simple.rs carries thousands of lines of parallel model/effort/provider logic that drifts from the Zig core (its own reasoning_effort plumbing, its own model settings state). Exactly the "TUI becomes the place where behavior is reimplemented" failure the doctrine warns about; iOS repeats the pattern.
External readers have no protocol: the serve test tearing on in-place session-file reads (fixed reader-side) shows clients reaching into engine-owned files instead of an API.
What exists already (this is an extraction, not a rewrite)
--json protocol mode and graff serve are ALREADY adapter-shaped: structured events over a stream, seq ids, resumable sessions (schema 0.10). The Agent turn loop + tool dispatch is the de-facto command core. The work is drawing the line and enforcing it:
Engine layer: the turn loop returns/emits structured events only (it largely does for --json); every say()/direct print inside core paths moves behind an event the adapter renders. Acceptance: -p, REPL, and --json show the same set of facts for the same run.
Command service: one Command -> CommandResult union shared by REPL slash-commands, --json control requests, and serve endpoints (today these are three parallel dispatchers: mainloop handleCommand, protocol handlers, serve routes).
Frontends as clients: REPL/TUI renders events; GUI and iOS migrate from reimplementation to the serve/--json protocol (the doctrine's CLI/REPL/TUI/MCP -> local protocol -> persistent engine diagram — serve IS that protocol).
CLI as the behavioral contract: the existing script suite (test-review-mode, test-serve-resume, pty tests) becomes the adapter-conformance suite; add a golden-events test asserting -p/--json/serve parity per run.
Staged and incremental: each say()-to-event conversion and each dispatcher unification stands alone. The payoff compounds — every frontend bug this week would have been either impossible or testable headlessly.
graff has six-plus frontends today — interactive REPL/TUI (mainloop + zigzag
graff repl), one-shot-p, the--jsonprotocol,graff serve(embedder), the Tauri GUI, and iOS — but no formal engine boundary. The core rule this issue proposes:Observed symptoms of the missing boundary (all from live sessions this week)
say()calls inside turn/tool logic render in the REPL but silently vanish in-p(the auto-promote notice is invisible in one-shot runs; only the trace note proves it fired). Structured results decided per-adapter would make every surface consistent.gui/src-tauri/src/runtime/simple.rscarries thousands of lines of parallel model/effort/provider logic that drifts from the Zig core (its own reasoning_effort plumbing, its own model settings state). Exactly the "TUI becomes the place where behavior is reimplemented" failure the doctrine warns about; iOS repeats the pattern.What exists already (this is an extraction, not a rewrite)
--jsonprotocol mode andgraff serveare ALREADY adapter-shaped: structured events over a stream, seq ids, resumable sessions (schema 0.10). The Agent turn loop + tool dispatch is the de-facto command core. The work is drawing the line and enforcing it:say()/direct print inside core paths moves behind an event the adapter renders. Acceptance:-p, REPL, and --json show the same set of facts for the same run.Command -> CommandResultunion shared by REPL slash-commands, --json control requests, and serve endpoints (today these are three parallel dispatchers: mainloop handleCommand, protocol handlers, serve routes).CLI/REPL/TUI/MCP -> local protocol -> persistent enginediagram — serve IS that protocol).Staged and incremental: each say()-to-event conversion and each dispatcher unification stands alone. The payoff compounds — every frontend bug this week would have been either impossible or testable headlessly.
🤖 Generated with Claude Code
https://claude.ai/code/session_01QbsV84fdmf39Bh2RF8LdPs