Make chat compaction resumable and secret-safe
Preserve authorization, evidence, pending work, loaded skills, and secret references in a fixed resumable-state summary. Clear the stale context-window cache after compaction and cover the prompt and persistence contracts with focused tests.
Alessandro committed
Jul 18, 2026 at 23:15 UTC
425cfc283b3c16e254a41f8d03d1f4cf7dd9a472
4 files changed
+66
-16
plugins/_chat_compaction/AGENTS.md
+3
@@ -18,7 +18,10 @@
18
- Preserve chat history integrity and persistence after compaction.
19
- Backup JSON and transcript artifacts must remain UTF-8 writable when chat content contains malformed Unicode such as lone surrogates.
20
- Keep generated summaries bounded by configured model and token limits.
21
+- Compacted summaries must be resumable task state: preserve the latest request, authorization boundaries, decisions, evidence, modified artifacts, pending jobs and their IDs, the next executable step, blockers, and checks not run.
22
- Preserve loaded skill names from `skill_instructions` metadata without copying full skill bodies into compacted summaries.
23
+- Preserve only secret references such as names, aliases, purposes, or storage locations; never preserve secret values.
24
+- Clear the cached context window after replacing history so stale transcript content is not persisted as active resumable state; the cache rebuilds on the next model turn.
25
- After replacing local history, clear the active Responses provider continuation while preserving stored response IDs for cleanup.
26
- Do not discard original context data unless the compaction flow explicitly owns that behavior.
27
plugins/_chat_compaction/helpers/compactor.py
+1
@@ -146,6 +146,7 @@ async def run_compaction(
146
agent.history = History(agent=agent)
147
agent.history.add_message(ai=True, content=compacted_content)
148
clear_responses_provider_state(agent)
149
+ agent.data.pop(Agent.DATA_NAME_CTX_WINDOW, None)
150
151
# Clear subordinate chain
152
agent.data.pop(Agent.DATA_NAME_SUBORDINATE, None)
plugins/_chat_compaction/prompts/compact.sys.md
+32
-16
@@ -1,18 +1,34 @@
1
-You are a conversation compactor. Produce the most concise summary possible while preserving critical information.
1
+You are a conversation compactor. Preserve the minimum state another agent needs to resume the task correctly without rereading the original conversation.
2
3
Rules:
4
-- Extract only: key decisions, final outcomes, actionable facts, unresolved items
5
-- Discard: intermediate reasoning, failed attempts, redundant exchanges, pleasantries
6
-- Use terse bullet points, not prose
7
-- Collapse related items into single lines
8
-- Keep exact values: file paths, config values, code identifiers, credentials, URLs
9
-- Preserve loaded skill names from skill_instructions metadata, but do not copy full skill bodies
10
-- Omit anything that can be re-derived from context
11
-- Group by topic, not chronology
12
-- No meta-commentary about the summarization
13
-- Target: 10-20% of original length
14
-
15
-Output format:
16
-- Markdown with short section headers
17
-- Bullet lists, no paragraphs
18
-- Code/paths in backticks inline, not fenced blocks unless multi-line
4
+- Capture the latest active user request, including anything that superseded an earlier request.
5
+- Preserve only explicit or clearly implied authorization and prohibitions; never broaden scope.
6
+- Separate decisions from assumptions and mark unverified assumptions.
7
+- Record completed work only with available evidence such as results, commands, tests, or artifact paths.
8
+- Preserve exact file paths, URLs, config values, code identifiers, job IDs, context IDs, and verification status.
9
+- Record pending workers or jobs with their IDs, state, and the next executable step.
10
+- Record blockers and every relevant check that was not run.
11
+- Preserve loaded skill names from `skill_instructions` metadata, but never copy skill bodies.
12
+- Never include passwords, API keys, tokens, credentials, private keys, session secrets, or other secret values. Preserve only a secret's name, purpose, storage location, or reference alias when needed.
13
+- Discard intermediate reasoning, redundant exchanges, pleasantries, and facts that can be safely re-derived.
14
+- Use terse bullets, no prose or meta-commentary. Target 10-20% of the original length when possible.
15
+
16
+Use exactly these sections in this order. Include `- None recorded.` for an empty section.
17
+
18
+## Current objective and latest user request
19
+
20
+## Authorized scope and prohibited actions
21
+
22
+## Decisions and assumptions
23
+
24
+## Completed work with evidence
25
+
26
+## Modified files and artifacts
27
+
28
+## Pending jobs and next executable step
29
+
30
+## Blockers and checks not run
31
+
32
+## Loaded skill names
33
+
34
+## Secret references
tests/test_chat_compaction.py
+30
@@ -72,6 +72,10 @@ class _CompactionAgent:
72
def __init__(self):
73
self.history = _CompactionHistory()
74
self.data = {
75
+ "ctx_window": {
76
+ "text": "pre-compaction transcript with secret values",
77
+ "tokens": 42,
78
+ },
79
"responses_state": {
80
"response_id": "resp_current",
81
"previous_response_id": "resp_previous",
@@ -86,6 +90,31 @@ class _CompactionAgent:
90
self.data[key] = value
91
92
93
+def test_compaction_prompt_is_resumable_task_state_without_secret_values():
94
+ prompt = (
95
+ PROJECT_ROOT / "plugins" / "_chat_compaction" / "prompts" / "compact.sys.md"
96
+ ).read_text(encoding="utf-8")
97
+ headings = [
98
+ "## Current objective and latest user request",
99
+ "## Authorized scope and prohibited actions",
100
+ "## Decisions and assumptions",
101
+ "## Completed work with evidence",
102
+ "## Modified files and artifacts",
103
+ "## Pending jobs and next executable step",
104
+ "## Blockers and checks not run",
105
+ "## Loaded skill names",
106
+ "## Secret references",
107
+ ]
108
+
109
+ positions = [prompt.index(heading) for heading in headings]
110
+ assert positions == sorted(positions)
111
+ assert "Never include passwords, API keys, tokens, credentials" in prompt
112
+ assert "Preserve only a secret's name, purpose, storage location" in prompt
113
+ assert "Keep exact values: file paths, config values, code identifiers, credentials" not in prompt
114
+ assert "next executable step" in prompt
115
+ assert "job IDs" in prompt
116
+
117
+
118
def test_pre_compaction_backup_sanitizes_surrogate_text(tmp_path, monkeypatch):
119
monkeypatch.setattr(
120
compactor,
@@ -193,3 +222,4 @@ async def test_manual_compaction_clears_active_responses_state(monkeypatch):
222
assert "response_id" not in state
223
assert "previous_response_id" not in state
224
assert state["response_ids"] == ["resp_previous", "resp_current"]
225
+ assert "ctx_window" not in agent.data