Skip to content

Commit 247d6ae

Browse files
committed
fix(savings): credit heeded nudge→compaction recoveries across the hook boundary
The quality-nudge follow-through now carries its fire timestamp and fill snapshot across the UserPromptSubmit→PostCompact process boundary via the per-session quality cache, so a compaction that follows a low-quality nudge is measured and credited (verified). The credit is consume-then-credit: fire-state is cleared and persisted before the credit is logged and only when that clear succeeds, making it idempotent and conservative under process failure. The cross-process carry set is centralized as _CARRY_KEYS, and the follow-through degrades safely on a corrupt cache. Ships as 5.9.3.
1 parent 8da7550 commit 247d6ae

7 files changed

Lines changed: 142 additions & 72 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"name": "token-optimizer",
1414
"source": "./",
1515
"description": "Audit, fix, and monitor Claude Code context window usage. Find the ghost tokens.",
16-
"version": "5.9.2",
16+
"version": "5.9.3",
1717
"author": {
1818
"name": "Alex Greenshpun",
1919
"url": "https://linkedin.com/in/alexgreensh"

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
},
88
"homepage": "https://github.com/alexgreensh/token-optimizer",
99
"repository": "https://github.com/alexgreensh/token-optimizer",
10-
"version": "5.9.2",
10+
"version": "5.9.3",
1111
"license": "PolyForm-Noncommercial-1.0.0",
1212
"keywords": ["token", "optimization", "context", "audit", "cost", "coach"]
1313
}

.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "token-optimizer",
3-
"version": "5.9.2",
3+
"version": "5.9.3",
44
"description": "Audit, monitor, and reduce Codex context waste with continuity helpers and explicit outline tools.",
55
"skills": "./skills/",
66
"interface": {

CHECKSUMS.sha256

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
f2794075c039e00370a99d3d2bd2b3f02eda3c2efcd9856eed7eea634c57a654 .claude-plugin/marketplace.json
2-
7cc4940ba79d29029ea67df156b9efac4ff358739b013fe70705d6fc8a7b5d72 .claude-plugin/plugin.json
3-
0cb56ebdf3193717707c4c091111539bd855096d8fdbcbcfeb4552c3e124cbfa .codex-plugin/plugin.json
1+
eabe0fa3ee227142a1e1bcd5c7d5ced2ab89af99aeee4b00f5179282b408a66d .claude-plugin/marketplace.json
2+
64e61ab6705f6cf0d99e521f2425304ba261e36ef6d559de9c74732b6711ea56 .claude-plugin/plugin.json
3+
9f0833202940cf758ae4f747413e0671dd062df0c24bbd41ecc71c74c541090d .codex-plugin/plugin.json
44
42a3d02dfdc46f985e877cc37e9f947ff0c082deeb991db958b339c90bdeef82 hooks/hooks.json
55
d7a8cff0f7af65bc38c274b5b4bd5251cb333b8503cf31b2fb97af18342d919a hooks/python-launcher.sh
66
848cf5672e1d4ca2036a05569748344429618933264143a24c8a7d61c69585ba hooks/run.py
@@ -80,7 +80,7 @@ b8345d767a48f40d275480973681baf56dc76329d8f3eaa6260966d6d6b787cd skills/token-o
8080
fef4a2385caf3c9495987007d16edf73a022cddbd0aecf2293025e3d9599996d skills/token-optimizer/scripts/detectors/websearch_routing.py
8181
6f023687c905db9be267aafcf2a85f08739bee8c0aae42b58eeae4b8889690b1 skills/token-optimizer/scripts/hook_io.py
8282
f647eabc7067499b74f72c4ba5c55738fdc18c6539a7a5b99c65d03126016d8d skills/token-optimizer/scripts/injection.py
83-
4c3cc853ae83fb7f05462c2e0b0fb5a37a647830f8131d6796f4954969cf802a skills/token-optimizer/scripts/measure.py
83+
fd960e8d0421dd29e4d1520e690721c7300eae308c77159061fbb3c7a67443a0 skills/token-optimizer/scripts/measure.py
8484
bd27835f85c5fd45c9cf67cb8b969038489249c56182ec2638a4f1223799b98f skills/token-optimizer/scripts/outline.py
8585
7e996f6c5d3c36ea0f0d70b8eec18080d0582c0155790637ac34abbd0b3e5057 skills/token-optimizer/scripts/plugin_env.py
8686
f115076cb26824fff4616c89f00d5542c76a2d2f0181cb6af3920cea40f647db skills/token-optimizer/scripts/read_cache.py

plugins/token-optimizer/.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "token-optimizer",
3-
"version": "5.9.2",
3+
"version": "5.9.3",
44
"description": "Audit, monitor, and reduce Codex context waste with continuity helpers and explicit outline tools.",
55
"skills": "./skills/",
66
"interface": {

plugins/token-optimizer/skills/token-optimizer/scripts/measure.py

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10005,7 +10005,7 @@ def setup_hook(dry_run=False):
1000510005

1000610006
# ========== Persistent Dashboard Daemon ==========
1000710007

10008-
TOKEN_OPTIMIZER_VERSION = "5.9.2" # Keep in sync with plugin.json + marketplace.json
10008+
TOKEN_OPTIMIZER_VERSION = "5.9.3" # Keep in sync with plugin.json + marketplace.json
1000910009
_DASHBOARD_CSP = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
1001010010
_DAEMON_RUNTIME = detect_runtime()
1001110011
_DAEMON_RUNTIME_SUFFIX = "codex" if _DAEMON_RUNTIME == "codex" else "claude"
@@ -17210,6 +17210,24 @@ def _maybe_progressive_checkpoint(fill_pct, cache_path, result, filepath):
1721017210
_LOOP_SESSION_CAP = 2
1721117211
_LOOP_LAST_MESSAGES = 4
1721217212

17213+
# Transient nudge/loop/warning state that must survive across the separate
17214+
# UserPromptSubmit and PostCompact hook processes. compute_quality_score() builds
17215+
# a fresh result each run, so quality_cache() carries these forward from the prior
17216+
# on-disk cache. Any new "_nudge_*"/"_loop_*"/warning-dedup key set in _maybe_nudge
17217+
# or _maybe_loop_warning MUST be registered here — a sibling key silently missing
17218+
# from this set is exactly what broke the nudge follow-through credit (A6).
17219+
_CARRY_KEYS = (
17220+
"_nudge_fill_pct_at_fire",
17221+
"_nudge_fire_epoch",
17222+
"_nudge_count",
17223+
"_nudge_last_epoch",
17224+
"_nudge_previous_score",
17225+
"_loop_warning_count",
17226+
"progressive_bands_captured",
17227+
"_last_fill_warn_level",
17228+
"_last_tool_call_warn_level",
17229+
)
17230+
1721317231

1721417232
def _check_realtime_loops(quality_data):
1721517233
"""Lightweight loop detection using already-parsed quality_data.
@@ -17565,10 +17583,7 @@ def quality_cache(throttle_seconds=120, warn_threshold=70, quiet=False, session_
1756517583

1756617584
_session_id = Path(cache_path).stem.replace("quality-cache-", "", 1) if cache_path else None
1756717585
result = compute_quality_score(quality_data, session_id=_session_id)
17568-
for carry_key in ("_nudge_fill_pct_at_fire", "_nudge_count", "_nudge_last_epoch",
17569-
"_nudge_previous_score", "_loop_warning_count",
17570-
"progressive_bands_captured", "_last_fill_warn_level",
17571-
"_last_tool_call_warn_level"):
17586+
for carry_key in _CARRY_KEYS:
1757217587
if carry_key in prev_result and carry_key not in result:
1757317588
result[carry_key] = prev_result[carry_key]
1757417589
result["total_messages"] = len(quality_data["messages"])
@@ -17687,33 +17702,53 @@ def quality_cache(throttle_seconds=120, warn_threshold=70, quiet=False, session_
1768717702
# Nudge follow-through: if PostCompact triggered this run (force=True)
1768817703
# and a nudge preceded the compact, measure the actual fill_pct recovery.
1768917704
if force and result.get("fill_pct", 0) > 0:
17690-
nudge_fill = result.get("_nudge_fill_pct_at_fire", 0)
17691-
if nudge_fill > 0:
17692-
current_fill = result["fill_pct"]
17693-
fill_delta = nudge_fill - current_fill
17694-
ft_sid = Path(cache_path).stem.replace("quality-cache-", "", 1) if cache_path else None
17695-
# A2 temporal gate: only credit a compaction that followed the nudge
17696-
# within the window. A compaction long after the nudge was not its
17697-
# follow-through, so it must not borrow the nudge's credit.
17698-
fire_epoch = result.get("_nudge_fire_epoch", 0)
17699-
within_window = fire_epoch and (time.time() - fire_epoch) <= _NUDGE_FOLLOWTHROUGH_WINDOW_SECONDS
17700-
# A2 dedup: if a checkpoint_restore already credited this session's
17701-
# recovery for this compaction (same SessionStart cycle), the nudge
17702-
# must not double-credit the same tokens.
17703-
already_credited = _checkpoint_restore_credited_recently(ft_sid)
17704-
if fill_delta > 5 and within_window and not already_credited:
17705-
context_size = detect_context_window()[0]
17706-
measured_tokens_recovered = int(context_size * fill_delta / 100)
17707-
_log_compression_event(
17708-
feature="quality_nudge",
17709-
original_text=" " * (measured_tokens_recovered * 4),
17710-
compressed_text=f"nudge_followthrough:fill={nudge_fill}->{current_fill}",
17711-
session_id=ft_sid,
17712-
detail=f"measured_recovery: fill {nudge_fill}%->{current_fill}% = {measured_tokens_recovered} tokens on {context_size} context",
17713-
verified=True,
17714-
)
17715-
result.pop("_nudge_fill_pct_at_fire", None)
17716-
result.pop("_nudge_fire_epoch", None)
17705+
# A6: the nudge fires during a prior UserPromptSubmit process; this
17706+
# follow-through runs in a SEPARATE PostCompact process. Fire-state reaches
17707+
# us via the atomic per-session quality cache (carried forward at the top of
17708+
# this function). Fall back to prev_result directly in case the carry did not
17709+
# run, so a heeded session is never misclassified as ignored. Wrapped so a
17710+
# corrupt cache value can never crash the host hook — it degrades to "no
17711+
# credit", not a traceback.
17712+
try:
17713+
nudge_fill = result.get("_nudge_fill_pct_at_fire") or prev_result.get("_nudge_fill_pct_at_fire", 0)
17714+
if nudge_fill > 0:
17715+
current_fill = result["fill_pct"]
17716+
fill_delta = nudge_fill - current_fill
17717+
ft_sid = Path(cache_path).stem.replace("quality-cache-", "", 1) if cache_path else None
17718+
# A2 temporal gate: only credit a compaction that followed the nudge
17719+
# within the window. A compaction long after the nudge was not its
17720+
# follow-through, so it must not borrow the nudge's credit.
17721+
fire_epoch = result.get("_nudge_fire_epoch") or prev_result.get("_nudge_fire_epoch", 0)
17722+
within_window = fire_epoch and (time.time() - fire_epoch) <= _NUDGE_FOLLOWTHROUGH_WINDOW_SECONDS
17723+
# A2 dedup: if a checkpoint_restore already credited this session's
17724+
# recovery for this compaction (same SessionStart cycle), the nudge
17725+
# must not double-credit the same tokens.
17726+
already_credited = _checkpoint_restore_credited_recently(ft_sid)
17727+
should_credit = fill_delta > 5 and within_window and not already_credited
17728+
# Consume the fire-state FIRST, then credit only if the consume was
17729+
# actually persisted. The fire-state lives in exactly one place (this
17730+
# per-session cache), so once it is cleared from disk no later
17731+
# PostCompact can re-enter this block for the same nudge — that makes
17732+
# the credit idempotent without a second DB query. If the process
17733+
# dies between here and the credit, or the clear-write fails, we skip
17734+
# the credit (a conservative under-count) rather than risk a
17735+
# double-count that would inflate the savings number.
17736+
result.pop("_nudge_fill_pct_at_fire", None)
17737+
result.pop("_nudge_fire_epoch", None)
17738+
consumed = _write_quality_cache(cache_path, result)
17739+
if should_credit and consumed:
17740+
context_size = detect_context_window()[0]
17741+
measured_tokens_recovered = int(context_size * fill_delta / 100)
17742+
_log_compression_event(
17743+
feature="quality_nudge",
17744+
original_text=" " * (measured_tokens_recovered * 4),
17745+
compressed_text=f"nudge_followthrough:fill={nudge_fill}->{current_fill}",
17746+
session_id=ft_sid,
17747+
detail=f"measured_recovery: fill {nudge_fill}%->{current_fill}% = {measured_tokens_recovered} tokens on {context_size} context",
17748+
verified=True,
17749+
)
17750+
except Exception:
17751+
pass
1771717752

1771817753
# Progressive checkpoints (v3.0)
1771917754
if _PROGRESSIVE_ENABLED and result.get("fill_pct", 0) > 0:

skills/token-optimizer/scripts/measure.py

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10005,7 +10005,7 @@ def setup_hook(dry_run=False):
1000510005

1000610006
# ========== Persistent Dashboard Daemon ==========
1000710007

10008-
TOKEN_OPTIMIZER_VERSION = "5.9.2" # Keep in sync with plugin.json + marketplace.json
10008+
TOKEN_OPTIMIZER_VERSION = "5.9.3" # Keep in sync with plugin.json + marketplace.json
1000910009
_DASHBOARD_CSP = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
1001010010
_DAEMON_RUNTIME = detect_runtime()
1001110011
_DAEMON_RUNTIME_SUFFIX = "codex" if _DAEMON_RUNTIME == "codex" else "claude"
@@ -17210,6 +17210,24 @@ def _maybe_progressive_checkpoint(fill_pct, cache_path, result, filepath):
1721017210
_LOOP_SESSION_CAP = 2
1721117211
_LOOP_LAST_MESSAGES = 4
1721217212

17213+
# Transient nudge/loop/warning state that must survive across the separate
17214+
# UserPromptSubmit and PostCompact hook processes. compute_quality_score() builds
17215+
# a fresh result each run, so quality_cache() carries these forward from the prior
17216+
# on-disk cache. Any new "_nudge_*"/"_loop_*"/warning-dedup key set in _maybe_nudge
17217+
# or _maybe_loop_warning MUST be registered here — a sibling key silently missing
17218+
# from this set is exactly what broke the nudge follow-through credit (A6).
17219+
_CARRY_KEYS = (
17220+
"_nudge_fill_pct_at_fire",
17221+
"_nudge_fire_epoch",
17222+
"_nudge_count",
17223+
"_nudge_last_epoch",
17224+
"_nudge_previous_score",
17225+
"_loop_warning_count",
17226+
"progressive_bands_captured",
17227+
"_last_fill_warn_level",
17228+
"_last_tool_call_warn_level",
17229+
)
17230+
1721317231

1721417232
def _check_realtime_loops(quality_data):
1721517233
"""Lightweight loop detection using already-parsed quality_data.
@@ -17565,10 +17583,7 @@ def quality_cache(throttle_seconds=120, warn_threshold=70, quiet=False, session_
1756517583

1756617584
_session_id = Path(cache_path).stem.replace("quality-cache-", "", 1) if cache_path else None
1756717585
result = compute_quality_score(quality_data, session_id=_session_id)
17568-
for carry_key in ("_nudge_fill_pct_at_fire", "_nudge_count", "_nudge_last_epoch",
17569-
"_nudge_previous_score", "_loop_warning_count",
17570-
"progressive_bands_captured", "_last_fill_warn_level",
17571-
"_last_tool_call_warn_level"):
17586+
for carry_key in _CARRY_KEYS:
1757217587
if carry_key in prev_result and carry_key not in result:
1757317588
result[carry_key] = prev_result[carry_key]
1757417589
result["total_messages"] = len(quality_data["messages"])
@@ -17687,33 +17702,53 @@ def quality_cache(throttle_seconds=120, warn_threshold=70, quiet=False, session_
1768717702
# Nudge follow-through: if PostCompact triggered this run (force=True)
1768817703
# and a nudge preceded the compact, measure the actual fill_pct recovery.
1768917704
if force and result.get("fill_pct", 0) > 0:
17690-
nudge_fill = result.get("_nudge_fill_pct_at_fire", 0)
17691-
if nudge_fill > 0:
17692-
current_fill = result["fill_pct"]
17693-
fill_delta = nudge_fill - current_fill
17694-
ft_sid = Path(cache_path).stem.replace("quality-cache-", "", 1) if cache_path else None
17695-
# A2 temporal gate: only credit a compaction that followed the nudge
17696-
# within the window. A compaction long after the nudge was not its
17697-
# follow-through, so it must not borrow the nudge's credit.
17698-
fire_epoch = result.get("_nudge_fire_epoch", 0)
17699-
within_window = fire_epoch and (time.time() - fire_epoch) <= _NUDGE_FOLLOWTHROUGH_WINDOW_SECONDS
17700-
# A2 dedup: if a checkpoint_restore already credited this session's
17701-
# recovery for this compaction (same SessionStart cycle), the nudge
17702-
# must not double-credit the same tokens.
17703-
already_credited = _checkpoint_restore_credited_recently(ft_sid)
17704-
if fill_delta > 5 and within_window and not already_credited:
17705-
context_size = detect_context_window()[0]
17706-
measured_tokens_recovered = int(context_size * fill_delta / 100)
17707-
_log_compression_event(
17708-
feature="quality_nudge",
17709-
original_text=" " * (measured_tokens_recovered * 4),
17710-
compressed_text=f"nudge_followthrough:fill={nudge_fill}->{current_fill}",
17711-
session_id=ft_sid,
17712-
detail=f"measured_recovery: fill {nudge_fill}%->{current_fill}% = {measured_tokens_recovered} tokens on {context_size} context",
17713-
verified=True,
17714-
)
17715-
result.pop("_nudge_fill_pct_at_fire", None)
17716-
result.pop("_nudge_fire_epoch", None)
17705+
# A6: the nudge fires during a prior UserPromptSubmit process; this
17706+
# follow-through runs in a SEPARATE PostCompact process. Fire-state reaches
17707+
# us via the atomic per-session quality cache (carried forward at the top of
17708+
# this function). Fall back to prev_result directly in case the carry did not
17709+
# run, so a heeded session is never misclassified as ignored. Wrapped so a
17710+
# corrupt cache value can never crash the host hook — it degrades to "no
17711+
# credit", not a traceback.
17712+
try:
17713+
nudge_fill = result.get("_nudge_fill_pct_at_fire") or prev_result.get("_nudge_fill_pct_at_fire", 0)
17714+
if nudge_fill > 0:
17715+
current_fill = result["fill_pct"]
17716+
fill_delta = nudge_fill - current_fill
17717+
ft_sid = Path(cache_path).stem.replace("quality-cache-", "", 1) if cache_path else None
17718+
# A2 temporal gate: only credit a compaction that followed the nudge
17719+
# within the window. A compaction long after the nudge was not its
17720+
# follow-through, so it must not borrow the nudge's credit.
17721+
fire_epoch = result.get("_nudge_fire_epoch") or prev_result.get("_nudge_fire_epoch", 0)
17722+
within_window = fire_epoch and (time.time() - fire_epoch) <= _NUDGE_FOLLOWTHROUGH_WINDOW_SECONDS
17723+
# A2 dedup: if a checkpoint_restore already credited this session's
17724+
# recovery for this compaction (same SessionStart cycle), the nudge
17725+
# must not double-credit the same tokens.
17726+
already_credited = _checkpoint_restore_credited_recently(ft_sid)
17727+
should_credit = fill_delta > 5 and within_window and not already_credited
17728+
# Consume the fire-state FIRST, then credit only if the consume was
17729+
# actually persisted. The fire-state lives in exactly one place (this
17730+
# per-session cache), so once it is cleared from disk no later
17731+
# PostCompact can re-enter this block for the same nudge — that makes
17732+
# the credit idempotent without a second DB query. If the process
17733+
# dies between here and the credit, or the clear-write fails, we skip
17734+
# the credit (a conservative under-count) rather than risk a
17735+
# double-count that would inflate the savings number.
17736+
result.pop("_nudge_fill_pct_at_fire", None)
17737+
result.pop("_nudge_fire_epoch", None)
17738+
consumed = _write_quality_cache(cache_path, result)
17739+
if should_credit and consumed:
17740+
context_size = detect_context_window()[0]
17741+
measured_tokens_recovered = int(context_size * fill_delta / 100)
17742+
_log_compression_event(
17743+
feature="quality_nudge",
17744+
original_text=" " * (measured_tokens_recovered * 4),
17745+
compressed_text=f"nudge_followthrough:fill={nudge_fill}->{current_fill}",
17746+
session_id=ft_sid,
17747+
detail=f"measured_recovery: fill {nudge_fill}%->{current_fill}% = {measured_tokens_recovered} tokens on {context_size} context",
17748+
verified=True,
17749+
)
17750+
except Exception:
17751+
pass
1771717752

1771817753
# Progressive checkpoints (v3.0)
1771917754
if _PROGRESSIVE_ENABLED and result.get("fill_pct", 0) > 0:

0 commit comments

Comments
 (0)