| 1 | # RFC: Output presentation filters without receipt mutation |
| 2 | |
| 3 | **Issue:** #4468 |
| 4 | |
| 5 | **Status:** Accepted for the v0.9.2 product boundary |
| 6 | |
| 7 | **Date:** 2026-07-26 |
| 8 | |
| 9 | ## Decision |
| 10 | |
| 11 | Codewhale will not run an arbitrary user script between a model response and |
| 12 | the canonical session record. Model-native assistant and thinking blocks remain |
| 13 | the durable audit, replay, cache-accounting, debugging, and provider-signature |
| 14 | source of truth. |
| 15 | |
| 16 | Output compression belongs to an explicit **presentation/export** layer. The |
| 17 | first supported controls are the existing safe surfaces: |
| 18 | |
| 19 | - TUI `show_thinking = false` hides thinking from the rendered transcript but |
| 20 | does not delete it from the canonical message/receipt path. |
| 21 | - `codewhale exec --output-format text|stream-json` selects a documented output |
| 22 | encoding. Structured output retains block identity so downstream tools can |
| 23 | select `thinking` or `text` without Codewhale rewriting either. |
| 24 | - Exports may add a future `--view canonical|response-only|thinking-only` |
| 25 | selector. A filtered export must label itself as a derived view and retain a |
| 26 | canonical session reference; it must never overwrite the session. |
| 27 | |
| 28 | This addresses the accessibility and automation need behind the proposed |
| 29 | CIPHER filter without turning untrusted scripts into invisible transcript |
| 30 | editors. |
| 31 | |
| 32 | ## Why |
| 33 | |
| 34 | ### Receipt fidelity |
| 35 | |
| 36 | The session is an audit record. Replacing content before persistence would make |
| 37 | it impossible to prove what the provider emitted, would corrupt signed |
| 38 | Anthropic thinking blocks, and could make usage/cost receipts disagree with the |
| 39 | visible record. Storing only the transformed form is rejected. Storing both |
| 40 | forms by default doubles sensitive data and creates ambiguous replay authority, |
| 41 | so it is also rejected. |
| 42 | |
| 43 | ### Prompt cache and replay |
| 44 | |
| 45 | Presentation filters do not change request-side token use. In particular, |
| 46 | `reasoning_replay_tokens` and provider-specific signed-thinking replay must use |
| 47 | the canonical form. A compression claim must separately measure: |
| 48 | |
| 49 | 1. terminal/export bytes; |
| 50 | 2. local storage bytes; |
| 51 | 3. request-side replay tokens; |
| 52 | 4. provider cache-hit behavior. |
| 53 | |
| 54 | Only the first is affected by the accepted v0.9.2 boundary. |
| 55 | |
| 56 | ### Streaming |
| 57 | |
| 58 | `HookEvent::ResponseDelta` is observer-only and arrives incrementally. A |
| 59 | block-level transformation would require buffering until block end, adding |
| 60 | latency and changing cancellation semantics. Presentation consumers may buffer |
| 61 | for their own output, but the engine continues to emit and persist canonical |
| 62 | deltas. |
| 63 | |
| 64 | ### Trust and failure |
| 65 | |
| 66 | No new arbitrary command execution is added. An external consumer may read |
| 67 | `stream-json` and apply its own bounded transform outside Codewhale. Its failure |
| 68 | cannot corrupt, delay, or replace the session. The canonical record therefore |
| 69 | provides the fail-open source automatically. |
| 70 | |
| 71 | ## Structured-output contract |
| 72 | |
| 73 | `stream-json` is the accessibility and integration surface: |
| 74 | |
| 75 | - events are JSON lines; |
| 76 | - response/thinking block identity remains explicit; |
| 77 | - tools may omit a block from their derived view, but must not describe that |
| 78 | view as the canonical session; |
| 79 | - no environment map, provider credential, hidden tool payload, or unrelated |
| 80 | transcript content is added for filtering; |
| 81 | - downstream tools should bound input, output, and processing time themselves. |
| 82 | |
| 83 | Example response-only presentation: |
| 84 | |
| 85 | ```sh |
| 86 | codewhale exec --output-format stream-json "..." \ |
| 87 | | jq -r 'select(.type == "message_delta") | .text // empty' |
| 88 | ``` |
| 89 | |
| 90 | The exact event names are versioned runtime output and callers should inspect a |
| 91 | fixture from their installed version rather than infer fields from this RFC. |
| 92 | |
| 93 | ## Rejected alternatives |
| 94 | |
| 95 | 1. **Pre-persistence output hook.** Rejected: mutates audit/replay authority. |
| 96 | 2. **Mutate only thinking.** Rejected: signed thinking and request replay still |
| 97 | require fidelity. |
| 98 | 3. **Store canonical plus transformed by default.** Rejected: duplicate |
| 99 | sensitive content and unclear authority. |
| 100 | 4. **Prompt the model to abbreviate.** The reporter measured 0% adoption and it |
| 101 | is not a reliable mechanical contract. |
| 102 | 5. **A separate `[hooks.output_filter]` table.** Rejected: duplicates the hook |
| 103 | schema while failing to solve the trust and receipt problems. |
| 104 | |
| 105 | ## Future additive work |
| 106 | |
| 107 | A future derived-export API may accept a declarative, non-executable selector |
| 108 | and write a receipt containing the source session id, source content hash, |
| 109 | selector, and derived output hash. Arbitrary executable transforms remain an |
| 110 | external pipeline unless a later security review defines sandboxing, |
| 111 | disclosure, latency, and dual-form retention semantics. |
| 112 | |
| 113 | ## Acceptance checks |
| 114 | |
| 115 | - Canonical session persistence and reasoning replay remain unchanged. |
| 116 | - `show_thinking` is documented as display-only. |
| 117 | - `stream-json` is documented as the safe machine-readable filter boundary. |
| 118 | - No hook stdout gains response-mutation authority. |
| 119 | - No new script, shell, credential, or network capability is introduced. |
| 120 | |
| 121 | Credit: the CIPHER measurements and the bounded stdin/stdout/fail-open proposal |
| 122 | came from @eugenicum in #4468. The v0.9.2 decision preserves that integration |
| 123 | use case while keeping Codewhale's canonical receipts trustworthy. |
| 124 |