CCR Lifetime and Retrieval Failure Handling
Learning Headroom
Why CCR Can't Keep Originals Forever
Builds on ← 5.3: CCR Reversible Compression Architecture
**Recall:** CCR compresses tool output / content, but keeps the original stashed locally with a retrieval marker — so the LLM can always ask for it back.
**The catch:** "kept" can't mean "kept forever."
- The local CCR store is process-local, tied to one proxy/session
- Storage isn't infinite — old originals need to expire
- An autonomous agent might run for **minutes or many hours**
**Central question of this node:**
> How long do we keep an original, and what happens if the agent asks for it *after* it's gone?
Where this leads → 5.5 will show how this TTL and recovery logic gets exposed as compression-strategy metadata for routing decisions; 7.1 needs this to design digest strategies for long repeated-wake runs.
The TTL: CCR's Local Store Lifetime
**Definition — CCR TTL (time-to-live):**
Each stored original has a lifetime \( T \) after which it is deleted from the local store.
\[ \text{expires\_at} = \text{stored\_at} + T \]
- Default: \( T = 1800 \) seconds (**30 minutes**)
- Configure with environment variable:
```bash
HEADROOM_CCR_TTL_SECONDS=7200 headroom proxy
```
- Verify effective value at:
`/v1/retrieve/stats` → `store.default_ttl_seconds`
**Why this shape?** \(T\) is a single number because the store doesn't try to predict *which* originals matter more — it just needs to outlive the run. Set \(T\) too small → originals vanish mid-task. Too large → local store bloats with dead weight.
**Rule of thumb:** size \(T\) for the **longest realistic autonomous run**, not the average one.
Worked Example: Sizing the TTL
**Scenario:** An autonomous coding agent runs unattended for up to **4 hours**. It compresses tool output (test logs, search results) throughout via SmartCrusher and ContentRouter.
**Step 1 — convert run duration to seconds:**
\[ 4 \text{ hours} \times 60 \ \frac{\text{min}}{\text{hr}} = 240 \text{ minutes} \]
\[ 240 \text{ minutes} \times 60 \ \frac{\text{sec}}{\text{min}} = 14400 \text{ seconds} \]
**Step 2 — add a 25% safety margin (agent could overrun):**
\[ 14400 \times 1.25 = 14400 + (14400 \times 0.25) \]
\[ 14400 \times 0.25 = 3600 \]
\[ 14400 + 3600 = 18000 \text{ seconds} \]
**Step 3 — set the env var:**
```bash
HEADROOM_CCR_TTL_SECONDS=18000 headroom proxy
```
**Step 4 — verify:**
```
GET /v1/retrieve/stats
→ store.default_ttl_seconds = 18000
```
**Contrast — leaving the default (1800s) for this run:**
Any original stored in the first 30 minutes is **gone** by hour 1 — while the agent may still be running for 3.5 more hours.
When Retrieval Fails: The Recovery Decision
**Scenario:** agent calls retrieval on a marker whose original has expired.
```mermaid
flowchart TD
A["Agent requests original via marker"] --> B{"Original still in store?"}
B -- Yes --> C["Return original content"]
B -- No, TTL expired --> D{"Can source be re-read?"}
D -- "Yes: file/log still exists" --> E["Source re-read fallback:
reopen source, regenerate digest"]
D -- "No: source gone/ephemeral" --> F["Digest regeneration:
rebuild summary from what remains
plus explicit note: original unavailable"]
```
**Two recovery paths (never silently fail):**
1. **Source re-read fallback** — if the original data source (file, API, log) is still accessible, re-fetch it and regenerate the compressed digest fresh.
2. **Digest regeneration** — if the source itself is gone, rebuild the best available summary from remaining context and flag explicitly that the full original could not be retrieved.
**Key principle:** assume a store can expire before the run finishes, then regenerate or re-read the source content — never let the agent silently receive nothing.
Worked Example: Choosing the Recovery Path
**Case A — SmartCrusher, tool output from a live API call**
- Original: JSON array of 500 search results, compressed by SmartCrusher
- Marker stored with content hash
- Agent retrieves after TTL expired
- Source: the search API is still callable with the same query
- **Action:** source re-read fallback → re-call the API, regenerate digest, compare hash if needed
**Case B — ContentRouter, a one-time streamed log**
- Original: 5-minute log stream captured live during a deploy
- That stream cannot be replayed — it's gone
- Agent retrieves after TTL expired
- **Action:** digest regeneration → rebuild summary from surrounding conversation context, explicitly mark: *"original log data unavailable past retention window"*
**Contrast:** Case A recovers the real data. Case B recovers only an honest approximation — never fabricate specifics that were only in the lost original.
Metadata, Strategy, and What's Next
**Compression strategy metadata carries the recovery info:**
Each CCR marker/strategy record should note:
- which store the original lives in (TTL scope)
- whether the source is **re-readable** or **ephemeral**
- which recovery path applies if retrieval fails
```mermaid
flowchart LR
S["Strategy metadata"] --> T["TTL for this store"]
S --> R{"Re-readable source?"}
R -- Yes --> F1["Source re-read fallback"]
R -- No --> F2["Digest regeneration"]
```
**This is exactly what informs routing decisions later.**
Where this leads →
- **5.5 Compression Strategy Metadata and Routing Authority**: this TTL + re-readability info becomes structured metadata the router uses to pick strategies
- **7.1 Designing a Repeated-Wake Digest Strategy**: long autonomous runs with repeated wakes need TTL sized to the *whole* run, plus a digest-regeneration plan built in from the start
Back to course