ADR 0049 · Result-frame enrichment toward the Claude Code contract
- Status: accepted
- Date: 2026-07-03
- Source:
docs/superpowers/specs/2026-07-03-result-frame-enrichment-design.md
Context
ADR 0025 defined the headless output protocol, wrapping "closely around Claude Code's documented shape" while explicitly declining "byte-identical compatibility because caliban is provider-agnostic" (and naming "add a compat translator" as its revisit path). Two gaps surfaced in practice (#222, QA dogfooding):
resultaggregated all assistant text. For success,resultwas populated fromfinal_text, which byte-concatenates every assistant text delta across the whole run. Any tool-using (multi-turn) run therefore put turn-1 narration at the front ofresultinstead of the final answer. Claude Code'sresultis the final assistant message only. (0025's own text calledresult"the assistant's reply", singular, but did not forbid the concat.)- Key drift. The frame emitted
turnsand flattotal_input_tokens/total_output_tokens; Claude Code usesnum_turnsand ausage{}object, and additionally emitsis_error,duration_ms,duration_api_ms. Renaming caliban's keys would break existing stream-json consumers.
Decision
We will enrich the result frame additively and fix the result value, amending ADR 0025's result-frame
shape:
result(success) = the final assistant message (HeadlessDriver.last_assistant_text, the last turn's reply), falling back to the accumulated text only when the per-turn tracker is empty.- Add
is_error: bool(truefor any non-successsubtype). - Add
duration_ms: u64— wall-clock run duration (per input frame in the stream-json multi-frame path). - Add the Claude-Code key names additively: emit
num_turns(=turns) and ausage: { input_tokens, output_tokens }object alongside the existingturns/total_input_tokens/total_output_tokens. Existing consumers are unaffected; CC drop-in consumers get their keys.
We will not rename or remove the legacy keys (that breaking step stays deferred to 0025's future compat
translator), and we defer duration_api_ms to a follow-up: accurate provider-API-only timing needs
instrumentation the headless driver doesn't have (it sees agent-core TurnEvents, which interleave tool
execution).
Consequences
- Positive:
resultis now the answer, not a running monologue — the headline bug is fixed. Stream-json is a cleaner Claude-Code drop-in (num_turns,usage,is_error,duration_msall present) without breaking any existing caliban consumer. - Negative: The frame carries some redundancy (
turns+num_turns, flat tokens +usage). A full CC key-rename is still outstanding, gated behind the 0025 compat-translator decision.duration_api_msis not yet emitted. - Revisit if: downstream consumers demand byte-for-byte CC parity (then do the breaking rename via the
0025 compat translator), or the deferred
duration_api_msfollow-up lands provider-level API timing.