# Core: resume ignores a legacy thinking level; empty compaction summaries no longer count as success; read_file's secret guard hardened

Since the thinking level became a per-turn run parameter (0.1.2), `session_meta` no longer records one — but resume still honored a `thinking_level` field found in an old Trace's meta, restoring it as the resumed Session's default. That compatibility read is removed: resume now always takes the level from the Agent's current config, and a legacy field is ignored outright. The one behavioral consequence is that a resumed legacy subagent session no longer keeps the level it inherited at spawn time — it follows the resuming Agent's configuration like everything else. Spawn-time inheritance in live runs, the per-turn picker, and what meta records are all unchanged.

## An empty compaction summary is a failure, not a result

A compaction request that came back with only thinking or a tool call — no text — used to be judged `completed`, wrapping an empty `<context_summary>` and discarding the old context: the one thing compaction must never do, since the Agent loses its task state (issue #83). Three fixes close it. The compaction request keeps its configuration — tools included — **byte-identical to ordinary requests** (the same frozen config object, pinned by a test): omitting or altering the tool list would change the request prefix and blow the provider's prompt cache at exactly the moment the context is largest, multiplying the compaction's cost tens of times. A `completed` response whose extracted summary trims to empty, or that answers with a tool call instead, is rejected: any committed tool calls are first **repaired** — one synthesized failed `tool_call_output` per call, in the ordinary tool-failure shape, written to the Trace and prepended to the resent input, so the provider-side pairing stays intact (and if the compaction is abandoned, the repairs move into the carry-over so the live LLM object is never left with a dangling `tool_use`) — then the request retries, with a dedicated cap of 5 rejections separate from the shared transport-reconnect budget; on exhaustion the compaction fails via `compaction_end(failed)` with the original context and Trace file retained. And resume no longer fabricates an empty summary from a textless compaction closure: such a closure is void and the original context replays, so Traces already bitten by the bug recover their task state too.

## Mid-task compaction stops re-sending what AgentHub already has

The empty-summary fix left one bounded hazard behind (issue #85): when auto-compaction fires mid-task, the pending turn input rides along in the compaction request — and once an attempt is **committed** into AgentHub's history, resending that input on a retry (or after giving up) hands strict providers duplicate `tool_result`s to reject. The rule is now a clean binary on one fact: if any compaction attempt committed, the carry is consumed — retries resend only the pairing repairs plus the prompt, and after abandonment the next request continues from committed state without re-sending the turn's outputs; if nothing committed, the previous carry-over is restored verbatim (object-identical). An abort inside the compaction window merges into the pending state instead of overwriting it, and replay reconstructs the same absorbed state after a restart, so live and resumed sessions agree.

## read_file's secret guard closes case and symlink bypasses

The builtin `read_file` refuses the secret stores (`.vault.toml`, `.project_config.toml`) by basename — its own guard, because `permission: "r"` makes the tool auto-approved under read-only approval while the shell tool stays rw-gated. That check compared the lexical basename case-sensitively, leaving two bypasses: a case-insensitive filesystem (macOS/Windows) opens `.VAULT.TOML` as the vault, and a symlink's own name says nothing about what it dereferences to. The guard now lowercases and checks both the lexical basename and the realpath target's basename. On a case-sensitive filesystem the lowercase compare can refuse a differently-cased sibling that is not the secret store — for a guard, that false positive is the right trade.
