# p0147c: Swagger compression + dynamic compaction trigger

- [Architecture] Swagger compression is a SwaggerSpec→SwaggerSpec transformation (RawJson rewrite) — examples stripped, descriptions truncated to 240 chars, unreferenced `components.schemas.*` pruned by transitive `$ref` reachability from `paths`. Typed `Endpoints`/`SecuritySchemes` are unchanged so every downstream consumer keeps observing the same API surface; only the prompt-bytes shrink.
- [Architecture] Two ContextKeys, not one: `SwaggerSpec` holds the (possibly compressed) default, `SwaggerSpecFull` holds the verbatim original. Skills choose per-skill which view to consume — keeps the default path lean without losing fidelity for scanners (ZAP / Spectral / Nuclei / payload-fuzz roles) that need the full schema. Migration of those Spawn* handlers to `SwaggerSpecFull` is deliberately out-of-scope for p0147c; both keys carry the same instance when no compression fires, so existing consumers stay correct.
- [Implementation] 100k-char activation threshold. Typical project specs are 10–50k; compression adds JSON re-serialization cost + loses example payloads (which LLMs use to disambiguate schemas). Only kick in when the original would risk truncation. Sample's real-world 291k case is the canonical "must shrink" example.
- [Implementation] Renamed the legacy `Application.Services.Handlers.SwaggerSpecCompressor` to `SwaggerSpecTextRenderer` (and its `Compress` method to `Render`). The legacy class returned a string for prompt injection — it was misnamed; the new spec-shrinker is the real "compressor". Treated as a tactical rename, not a redesign — single internal caller (`ApiSkillRoundHandler`) and its test file updated in lockstep.
- [Architecture] Dynamic compaction trigger: primary signal is token-pressure (`estimatedTokens ≥ MaxContextTokensTriggerRatio × MaxContextTokens`, default ratio 0.7); the existing iteration cap (`ThresholdIterations`) stays as a defensive upper bound. Token counts are estimates (±5%); a pathological prompt could keep iteration count low while pushing real tokens over the cap, or the estimator could undershoot. Defence in depth.
- [Implementation] Trigger predicate exposed as a pure static method (`ClaudeContextCompactor.ShouldCompact` and `OpenAiContextCompactor.ShouldCompact`) so unit tests exercise the threshold logic without spinning up the summarizer client. OpenAI's internal `ShouldFire` now delegates to it; Claude's compactor exposes the predicate for the agentic loop to call (the loop integration is left to the caller — the predicate is the contract).
- [Implementation] `MaxContextTokensTriggerRatio = 0` disables the token trigger entirely (iteration cap only). Lets a deployment opt out of dynamic compaction without removing the property — useful for the rare case where a model has unreliable token accounting.
- [TradeOff] Compression discards example payloads even when they would have fit. Examples are valuable schema-disambiguation signal for LLMs, but they are also the single biggest size contributor and there's no cheap way to keep "some" examples — a per-endpoint scorer would push this past a one-shot phase. Accept the loss above the threshold; small specs (the common case) keep their examples.
- [TradeOff] Description truncation cuts at the first sentence boundary inside the 240-char window when one exists; otherwise hard-cuts at 240 chars. Hard-cuts can land mid-word — the trailing "…" marker signals truncation to the LLM, which is enough; smarter word-boundary handling would add complexity for marginal quality gain.
