Repository navigation
Description
What to build
Keep Claude Code and Grok Build Runs reporting the same progress, final messages, spend and failures, with the same Resume decisions, while deepening their Harness stream interpretation into one module. Both Harness adapters select their own dialect directly; interpreting Grok no longer requires rewriting events into Claude's format, reparsing them, or replacing Claude's completed facts.
Acceptance criteria
- One Harness stream interpretation module owns shared frame decoding and the two dialects' rules behind the existing Interpretation interface. Neither Harness adapter depends on the other's concrete decoder.
- Each incoming line is parsed once. Grok's tool-name and path handling reads the parsed frame directly, without a mutation/serialization/reparse bridge.
- Progress lines retain their order, wording, truncation, first-init working-directory handling and one-time session-start behavior. Unknown and malformed frames remain harmless.
- Final text, session-id precedence, summaries, failure diagnostics and empty/missing values retain the compatibility rules in Decisions, including repeated and incomplete results.
- Claude killed-work descriptions and ordering continue to drive the existing single Resume; Grok returns no killed-work evidence, including when fed Claude task frames. Failed turns continue to supply no successful Resume facts.
- Existing interpretation tests are moved as needed and remain at the Interpretation interface. Add characterization cases for the cross-dialect differences listed in Testing decisions, then delete the superseded decoder implementations and any duplicate tests.
- Focused Harness, Grok session, progress and background-work tests pass. Run formatting, clippy with warnings denied, and the full Rust test suite before delivery.
Blocked by
None (can start immediately).
Decisions
Design tree and settled rounds
- Constraints and size. Preserve observable behavior and the existing process, logging, completion and Resume rules. Deliver the complete replacement as one standalone Ticket: only two decoders and their adapter construction change, with an existing test seam, so a single fresh context window is enough. Reason: this gives one verifiable change without an intermediate compatibility implementation or artificial dependency graph. This is the highest-ranked uncovered Strong recommendation because the direct dependence on another Harness's complete state is concrete friction in a recent hot spot; no open issue proposes this deepening.
- Module and seam. Put the shared decoder inside Harness interpretation, with two private dialect selections, Claude and Grok. Both existing adapters construct it directly; callers continue to use Interpretation's line condensation and consuming completion. Keep the existing internal Decoder seam and its Facts result. Reason: the interface is already the test surface, and these two real adapters justify the variation. A new configurable policy interface would add knowledge for callers without depth.
- Ownership. The shared implementation owns parsing, first-init frame handling, tool progress and final-result text. Private dialect state owns outcome, diagnostics, usage selection, session-id fallback and killed-work evidence. Construct completed facts once. Remove the nested Claude decoder and both superseded concrete decoder arrangements; do not stop at extracting a parsed-value helper. Reason: shared framing and its consequences gain locality. The deletion test passes: deleting the replacement would spread shared frame and lifecycle rules back across the two adapters; those rules belong in the deep module.
- Parsing and tools. Decode a line to a JSON value once and inspect it directly. For Grok, treat
bashandrun_terminal_commandas Bash, preserve native Bash, and leave other tool names intact. A stringpathtakes precedence overfile_path, including an empty string. Then preserve detail precedence: file path, pattern, description, URL, query. Claude uses its existing names and fields. Keep the shared command description and Unicode truncation behavior. Reason: the bridge can disappear without changing tool progress or leaking normalization to adapter callers. - Initialization and ids. First init alone determines working directory, subscription marker and the init id; later init frames neither replace nor fill those shared init fields. Emit session-start progress once. Claude uses only that first-init id. Grok additionally retains the first string id from any parsed event, including unknown types, and prefers the first-init id over that fallback at completion. Preserve empty strings as present ids. Reason: Resume must continue to receive exactly the id selected today, including streams without init data.
- Result text. Every result replaces the final message with its string result, including an empty string; a missing or non-string result clears it. Reason: the last result supplies the Architecture review conclusion and the session's last reply; retaining an older message would change those outcomes.
- Spend. Retain the last complete unsigned-turn-count/numeric-cost pair across incomplete later results. Claude reports that pair, including zero cost, with the existing first-init
apiKeySource == "none"marker addingat API prices. Grok recalculates whether cost is positive and clears/rebuilds token usage on every result. Token usage requires unsigned input and output counts; each cache count defaults to zero. With positive last-result cost, Grok prefers the retained complete pair, falling back to last-result tokens only when no pair exists. With absent, zero or negative last-result cost, Grok uses only last-result tokens. Preserve Grok's existingat API pricesmarker when its selected pair came from an init markednone. Reason: this refactor must preserve existing summaries, including an earlier complete pair surviving an incomplete positive-cost result; correcting reporting policy would be separate work. - Outcome and diagnostics. For both dialects, each result replaces failure: a subtype other than success or a true error flag fails. Claude ignores standalone error frames and supplies no diagnostic. Grok standalone error frames set failure and replace the diagnostic with the string message, including empty; a missing/non-string message clears it. Each Grok result prefers a nonempty collection of string entries from its errors array, joined with newlines, even on success and even if an entry is empty. Otherwise a failing result uses nonempty result text, or retains the prior diagnostic. A successful result can recover after a standalone error while retaining its diagnostic for a later unsuccessful process exit. Failing Grok error/result frames emit their current error progress line, preserving the empty-string case. Reason: failure selection and diagnostic fallback are distinct facts today and must not be collapsed into a different policy.
- Killed work. Keep task tracking only in the Claude dialect: retain descriptions by id across results, record killed updates and stopped notifications in occurrence order, deduplicate ids, and clear killed evidence at every result. Preserve description/summary/id fallback. Grok never records killed-work evidence. Reason: only Claude's task frames justify these Resume facts; running that state machine for Grok adds work and coupling without capability.
- Completion and scope. Let the existing Interpretation module continue to combine decoder facts with execution status, interruption and retained records, while Sessions continues to own Resume. Keep invocation flags, Model/Effort checks and other Harness decoders outside this Ticket. Reason: those modules are already deepened and their decisions do not depend on this replacement.
- Domain and ADRs. Name the module for Harness interpretation using the existing Harness vocabulary. No new domain term or ADR is needed: this is a reversible implementation arrangement behind an existing interface, with no changed trust or execution policy. Reason: ADRs 0012 and 0013 continue to apply without reopening a decision; there is no documentation side effect to assign.
The root constraints were settled first; module ownership and the seam followed; all dialect rules were then settled from the current code and tests; migration, tests and documentation consequences were settled last. The design frontier is empty.
Testing decisions
- Highest existing seam. Exercise each real Harness adapter's Interpretation with raw frames and consuming completion, asserting progress lines, summaries, final messages and outcomes. Move existing Claude/Grok behavioral tests to the shared implementation's test location as needed without changing what they observe. Reason: callers and tests cross the same seam, so tests survive further internal refactors; there is no new mock adapter or test-only interface.
- Characterize the differences before replacing the implementation. Cover both dialects in paired cases for first/missing/repeated init and empty ids; Grok any-event id fallback versus init precedence; positive/zero/absent cost and incomplete later totals; the first-init subscription marker in both dialects; missing/empty final text; standalone error followed by success and unsuccessful process exit; diagnostic precedence and empty strings; tool aliases and competing path fields; Claude task evidence versus Grok's empty evidence. Reason: most ordinary cases already have coverage, while these less obvious rules could be lost when removing the nested decoder.
- Process verification and test deletion. Preserve the existing Grok session, progress and background-work scenarios that verify raw Session logs, Architecture review final text, failure causes and Claude Resume behavior. Replace superseded decoder-level coverage rather than adding a parallel suite for the old implementations. Use the repository's normal formatting, clippy and full-suite checks for delivery. Reason: these scenarios verify that the unchanged outer modules still consume the new implementation correctly, while duplicated suites would reduce locality.
Review baseline at 8dd33a82fd3516c8c6d0516089c9682555789940: all 120 Harness tests passed, and all 18 Grok session, progress and background-work scenarios passed. These establish the current behavior; they are not validation of an implemented refactor.
Architecture review
Strength: Strong · Dependencies: in-process
Files: src/harness/claude/stream.rs, src/harness/grok/stream.rs, src/harness/claude.rs, src/harness/grok.rs, src/harness/interpretation.rs, src/harness/interpretation_tests.rs, tests/grok_sessions.rs, tests/progress_lines.rs, tests/background_work.rs
Before
flowchart LR
CA[Claude adapter] --> CF[Claude frames]
GA[Grok adapter] --> GF[Grok frames]
subgraph CM[Claude stream interpretation]
CF --> CR[Claude rules]
end
subgraph GM[Grok stream interpretation]
GF --> GR[Grok rules]
end
GF -. rewrite and reparse .-> CF
GR -. replace completed facts .-> CR
classDef leak stroke:#dc2626,stroke-width:2px;
class GM leak
After
flowchart LR
CA[Claude adapter] --> M
GA[Grok adapter] --> M
subgraph M[Harness stream interpretation]
CF[Claude frames] --> F[Shared frames]
GF[Grok frames] --> F
F --> CR[Claude rules]
F --> GR[Grok rules]
end
classDef deep stroke-width:3px;
class M deep
Problem: Grok's shallow decoder module borrows Claude's entire implementation and then replaces completed facts, spreading knowledge of one Harness's behavior across both modules.
Solution: Deepen Harness stream interpretation behind the existing interface and Decoder seam, with the two existing adapters selecting explicit private dialect rules.
Wins
- depth: one interface, two Harnesses
- locality: framing and rules stay together
- leverage: tests cover both adapters
- Delete the rewrite/reparse bridge
Activity