All skills
Skillintermediate

GCOMPACTION.md — Design & Architecture (TABLED)

**Target path on approval:** `docs/designs/GCOMPACTION.md`

Claude Code Knowledge Pack7/10/2026

Overview

GCOMPACTION.md — Design & Architecture (TABLED)

Target path on approval: docs/designs/GCOMPACTION.md

This is the preserved design artifact for gstack compact. Everything above the first --- divider below gets extracted verbatim to docs/designs/GCOMPACTION.md on plan approval. Everything after that divider is archived research (office hours + competitive deep-dive + eng-review notes + codex review + research findings) that informed the design.


Status: TABLED (2026-04-17) — pending Anthropic updatedBuiltinToolOutput API

Why tabled. The v1 architecture assumed a Claude Code PostToolUse hook could REPLACE the tool output that enters the model's context for built-in tools (Bash, Read, Grep, Glob, WebFetch). Research on 2026-04-17 confirmed this is not possible today.

Evidence:

  1. Official docs (https://code.claude.com/docs/en/hooks): The only output-replace field documented for PostToolUse is hookSpecificOutput.updatedMCPToolOutput, and the docs explicitly state: "For MCP tools only: replaces the tool's output with the provided value." No equivalent field exists for built-in tools.
  2. Anthropic issue #36843 (OPEN): Anthropic themselves acknowledge the gap. "PostToolUse hooks can replace MCP tool output via updatedMCPToolOutput, but there is no equivalent for built-in tools (WebFetch, WebSearch, Bash, Read, etc.)... They can only add warnings via decision: block (which injects a reason string) or additionalContext. The original malicious content still reaches the model."
  3. RTK mechanism (source-reviewed at src/hooks/init.rs:906-912 and hooks/claude/rtk-rewrite.sh:83-100): RTK is NOT a PostToolUse compactor. It's a PreToolUse Bash matcher that rewrites tool_input.command (e.g., git statusrtk git status). The wrapped command produces compact stdout itself. RTK README confirms: "the hook only runs on Bash tool calls. Claude Code built-in tools like Read, Grep, and Glob do not pass through the Bash hook, so they are not auto-rewritten." RTK is Bash-only by architectural constraint, not by choice.
  4. tokenjuice mechanism (source-reviewed at src/core/claude-code.ts:160, 491, 540-549): tokenjuice DOES register PostToolUse with matcher: "Bash" but has no real output-replace API available — it hijacks decision: "block" + reason to inject compacted text. Whether this actually reduces model-context tokens or just overlays UI output is disputed. tokenjuice is also Bash-only.
  5. Read/Grep/Glob execute in-process inside Claude Code and bypass hooks entirely. Wedge (ii) "native-tool coverage" was architecturally impossible from day one regardless of replacement API.

Consequence. Both wedges are dead in their original form:

  • Wedge (i) "Conditional LLM verifier" — still technically possible, but only for Bash output, via PreToolUse command wrapping (RTK's mechanism). The verifier stops being a differentiator once we're also Bash-only.
  • Wedge (ii) "Native-tool coverage" — impossible today. Read/Grep/Glob don't fire hooks. Even if they did, no output-replace field exists.

Decision. Shelve gstack compact entirely. Track Anthropic issue #36843 for the arrival of updatedBuiltinToolOutput (or equivalent). When that API ships, this design doc + the 15 locked decisions below + the research archive at the bottom become the unblocking artifacts for a fresh implementation sprint.

If un-tabling: Start from the "Decisions locked during plan-eng-review" block below — most remain valid. Then re-verify the hooks reference against the newly-shipped API, update the Architecture data-flow diagram to use whatever real output-replacement field exists, and re-run /codex review against the revised plan before coding.

What we're NOT doing:

  • Not shipping a Bash-only PreToolUse wrapper. That's RTK's product; they're at 28K stars and 3 years of rule scars. No wedge.
  • Not shipping the decision: block + reason hack. Undocumented behavior, Anthropic could break it, and the model may still see the raw output alongside the compacted overlay — context savings are disputed.
  • Not shipping B-series benchmark in isolation. Without a working compactor, there's nothing to benchmark.

Cost of tabling: ~0. No code was written. The design doc + research + decisions remain as a ready-to-unblock artifact.


Decisions locked during plan-eng-review (2026-04-17)

Preserved for the un-tabling sprint if/when Anthropic ships the built-in-tool output-replace API.

Summary of every decision made during the engineering review. Full rationale is preserved throughout the sections below; this block is the single source of truth if anything else drifts.

Scope (Section 0):

  1. Claude-first v1. Ship compact + rules + verifier on Claude Code only. Codex + OpenClaw land at v1.1 after the wedge is proven on the primary host. Cuts ~2 days of host integration and derisks launch. The original "wedge (ii) native-tool coverage" claim applies to Claude Code at v1; we make no cross-host claim until v1.1.
  2. 13-rule launch library. v1 ships tests (jest/vitest/pytest/cargo-test/go-test/rspec) + git (diff/log/status) + install (npm/pnpm/pip/cargo). Build/lint/log families defer to v1.1, driven by gstack compact discover telemetry from real users.
  3. Verifier default ON at v1.0. failureCompaction trigger (exit≠0 AND >50% reduction) is enabled out of the box. The verifier IS the wedge — defaulting it off hides the differentiating feature. Trigger bounds already keep expected fire rate ≤10% of tool calls.

Architecture (Section 1): 4. Exact line-match sanitization for Haiku output. Split raw output by \ , put lines in a set, only append lines from Haiku that appear verbatim in that set. Tightest adversarial contract; prompt-injection attempts cannot slip in novel text. 5. Layered failureCompaction signal. Prefer exitCode from the envelope; if the host omits it, fall back to /FAIL|Error|Traceback|panic/ regex on the output. Log which signal fired in meta.failureSignal ("exit" | "pattern" | "none"). Pre-implementation task #1 still verifies Claude Code's envelope empirically, but the system no longer breaks if it doesn't. 6. Deep-merge rule resolution. User/project rules inherit built-in fields they don't override. Escape hatch: "extends": null in a rule file triggers full replacement semantics. Matches the mental model of eslint/tsconfig/.gitignore — override a piece without losing the rest.

Code quality (Section 2): 7. Per-rule regex timeout, no RE2 dep. Run each rule's regex via a 50ms AbortSignal budget; on timeout, skip the rule and record meta.regexTimedOut: [ruleId]. Avoids a WASM dependency and keeps rule-author syntax unconstrained. 8. Pre-compiled rule bundle. gstack compact install and gstack compact reload produce ~/.gstack/compact/rules.bundle.json (deep-merged, regex-compiled metadata cached). Hook reads that single file instead of parsing N source files. 9. Auto-reload on mtime drift. Hook stats rule source files on startup; if any source file is newer than the bundle, rebuild in-line before applying. Adds ~0.5ms/invocation but eliminates the "I edited a rule and nothing changed" footgun. 10. Expanded v1 redaction set. Tee files redact: AWS keys, GitHub tokens (ghp_/gho_/ghs_/ghu_), GitLab tokens (glpat-), Slack webhooks, generic JWT (three base64 segments), generic bearer tokens, SSH private-key headers (-----BEGIN * PRIVATE KEY-----). Credit cards / SSNs / per-key env-pairs deferred to a full DLP layer in v2.

Testing (Section 3): 11. P-series gate subset. v1 gate-tier P-tests: P1 (binary garbage), P3 (empty output), P6 (RTK-killer critical stack frame), P8 (secrets to tee), P15 (hook timeout), P18 (prompt injection), P26 (malformed user rule JSON), P28 (regex DoS), P30 (Haiku hallucination). Remaining 21 P-cases grow R-series as real bugs hit. 12. Fixture version-stamping. Every golden fixture has a toolVersion: frontmatter. CI warns when fixture toolVersion ≠ currently installed. No more calendar-based rotation. 13. B-series real-world benchmark testbench (hard v1 gate). New component compact/benchmark/ scans ~/.claude/projects/**/*.jsonl, ranks the noisiest tool calls, clusters them into named scenarios, replays the compactor against them, and reports reduction-by-rule-family. v1 cannot ship until B-series on the author's own 30-day corpus shows ≥15% reduction AND zero critical-line loss on planted bugs. Local-only; never uploads. Community-shared corpus is v2.

Performance (Section 4): 14. Revised latency budgets. Bun cold-start on macOS ARM is 15-25ms; the original 10ms p50 target was unrealistic. New budgets: <30ms p50 / <80ms p99 on macOS ARM, <20ms p50 / <60ms p99 on Linux (verifier off). Verifier-fires budget stays <600ms p50 / <2s p99. Daemon mode is a v2 option gated on B-series showing cold-start hurts session savings. 15. Line-oriented streaming pipeline. Readline over stdin → filter → group → dedupe → ring-buffered tail truncation → stdout. Any single line >1MB hits P9 (truncate to 1KB with [... truncated ...] marker). Caps memory at 64MB regardless of total output size.

Every row above is a MUST in the implementation. Drift requires a new eng-review.


Summary

gstack compact was designed as a PostToolUse hook that reduces tool-output noise before it reaches an AI coding agent's context window. Deterministic JSON rules would shrink noisy test runners, build logs, git diffs, and package installs. A conditional Claude Haiku verifier would act as a safety net when over-compaction risk was high.

Current status: TABLED. See "Status" section above. The architecture depends on a Claude Code API (updatedBuiltinToolOutput or equivalent for built-in tools) that does not exist as of 2026-04-17. Anthropic issue #36843 tracks the gap.

Intended goal (preserved for the un-tabling sprint): 15–30% tool-output token reduction per long session, with zero increase in task-failure rate.

Original wedge (vs RTK, the 28K-star incumbent) — both invalidated by research:

  1. Conditional LLM verifier. Still technically viable via PreToolUse command wrapping, but only for Bash. Stops being a differentiator once we're Bash-only. Reconsider if the built-in-tool API arrives.
  2. Native-tool coverage. Architecturally impossible today. Read/Grep/Glob execute in-process inside Claude Code and do not fire hooks. Even for tools that do fire PostToolUse, no output-replacement field exists for non-MCP tools.

Original positioning (now moot): "RTK is fast. gstack compact is fast AND safe, and it covers every tool in your toolbox, not just Bash."

Non-goals

  • Summarizing user messages or prior agent turns (Claude's own Compaction API owns that).
  • Compressing agent response output (caveman's layer).
  • Caching tool calls to avoid re-execution (token-optimizer-mcp's layer).
  • Acting as a general-purpose log analyzer.
  • Replacing the agent's own judgement about when to re-run a command with GSTACK_RAW=1.

Why this is worth building

Problem is measured, not hypothetical.

  • Chroma research (2025) tested 18 frontier models. Every model degrades as context grows. Rot starts well before the window limit — a 200K model rots at 50K.
  • Coding agents are the worst case: accumulative context + high distractor density + long task horizon. Tool output is explicitly named as a primary noise source.
  • The market has voted: Anthropic shipped Opus 4.6 Compaction API; OpenAI shipped a compaction guide; Google ADK shipped context compression; LangChain shipped autonomous compression; sst/opencode has built-in compaction. The hybrid deterministic + LLM pattern is industry consensus.

Existing field (what gstack compact joins and differentiates from):

ProjectStarsLicenseLayerThreatNote
RTK (rtk-ai/rtk)28KApache-2.0Tool outputPrimary benchmarkPure Rust, Bash-only, zero LLM
caveman34.8KMITOutput tokensDifferent axisTerse system prompt; pairs WITH us
claude-token-efficient4.3KMITResponse verbosityDifferent axisSingle CLAUDE.md
token-optimizer-mcp49MITMCP cachingDifferent axisPrevents calls rather than compresses output
tokenjuice~12MITTool outputToo new2 days old; inspired our JSON envelope
6-Layer Token Savings StackPublic gistRecipeZeroDocumentation; validates stacked compaction thesis

RTK is the only direct competitor. Everything else compresses a different token source.

License compatibility: Every referenced project is permissive-licensed (MIT or Apache-2.0) and compatible with gstack's MIT license. No AGPL, GPL, or other copyleft dependencies. See the "License & attribution" section below for the clean-room policy.

Architecture

Data flow

┌─────────────────────────────────────────────────────────────────┐
│  Host (Claude Code / Codex / OpenClaw)                          │
│  ─────────────────────────────────────────                      │
│  1. Agent requests tool call: Bash|Read|Grep|Glob|MCP           │
│  2. Host executes tool                                          │
│  3. Host invokes PostToolUse hook with: {tool, input, output}   │
└────────────────────┬────────────────────────────────────────────┘
                     │ stdin (JSON envelope)
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│  gstack-compact hook binary                                     │
│  ───────────────────────────                                    │
│  a. Parse envelope                                              │
│  b. Match rule by (tool, command, pattern)                      │
│  c. Apply rule primitives: filter / group / truncate / dedupe   │
│  d. Record reduction metadata                                   │
│  e. Evaluate verifier triggers                                  │
│  f. If trigger met: call Haiku, append preserved lines          │
│  g. On failure exit code: tee raw to ~/.gstack/compact/tee/...  │
│  h. Emit JSON envelope to stdout                                │
└────────────────────┬────────────────────────────────────────────┘
                     │ stdout (JSON envelope)
                     ▼
              Host substitutes compacted output into agent context

Rule resolution

Three-tier hierarchy (highest precedence wins), same pattern as tokenjuice and gstack's existing host-config-export model:

  1. Built-in rules: compact/rules/ shipped with gstack
  2. User rules: ~/.config/gstack/compact-rules/
  3. Project rules: .gstack/compact-rules/

Rules match tool calls by rule ID. A project rule with ID tests/jest overrides the built-in