@miller-tech/uap 2.1.7 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/config/capacity-policy.json +32 -0
- package/config/classify-thresholds.json +9 -0
- package/config/supervise-policy.json +13 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +87 -2
- package/dist/bin/cli.js.map +1 -1
- package/dist/capacity/policy.d.ts +67 -0
- package/dist/capacity/policy.d.ts.map +1 -0
- package/dist/capacity/policy.js +133 -0
- package/dist/capacity/policy.js.map +1 -0
- package/dist/capacity/probe.d.ts +23 -0
- package/dist/capacity/probe.d.ts.map +1 -0
- package/dist/capacity/probe.js +93 -0
- package/dist/capacity/probe.js.map +1 -0
- package/dist/classify/baseline.d.ts +24 -0
- package/dist/classify/baseline.d.ts.map +1 -0
- package/dist/classify/baseline.js +140 -0
- package/dist/classify/baseline.js.map +1 -0
- package/dist/classify/index.d.ts +30 -0
- package/dist/classify/index.d.ts.map +1 -0
- package/dist/classify/index.js +84 -0
- package/dist/classify/index.js.map +1 -0
- package/dist/classify/shadow.d.ts +34 -0
- package/dist/classify/shadow.d.ts.map +1 -0
- package/dist/classify/shadow.js +33 -0
- package/dist/classify/shadow.js.map +1 -0
- package/dist/classify/types.d.ts +64 -0
- package/dist/classify/types.d.ts.map +1 -0
- package/dist/classify/types.js +37 -0
- package/dist/classify/types.js.map +1 -0
- package/dist/cli/classify.d.ts +24 -0
- package/dist/cli/classify.d.ts.map +1 -0
- package/dist/cli/classify.js +94 -0
- package/dist/cli/classify.js.map +1 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +26 -0
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/doctor.d.ts +9 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +49 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/review.d.ts +12 -0
- package/dist/cli/review.d.ts.map +1 -0
- package/dist/cli/review.js +118 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli/supervise.d.ts +8 -0
- package/dist/cli/supervise.d.ts.map +1 -0
- package/dist/cli/supervise.js +76 -0
- package/dist/cli/supervise.js.map +1 -0
- package/dist/delivery/convergence-loop.d.ts +8 -0
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +1 -0
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/delivery-result.d.ts.map +1 -1
- package/dist/delivery/delivery-result.js +5 -0
- package/dist/delivery/delivery-result.js.map +1 -1
- package/dist/delivery/gate-evidence.d.ts +143 -0
- package/dist/delivery/gate-evidence.d.ts.map +1 -0
- package/dist/delivery/gate-evidence.js +239 -0
- package/dist/delivery/gate-evidence.js.map +1 -0
- package/dist/delivery/index.d.ts +1 -0
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +1 -0
- package/dist/delivery/index.js.map +1 -1
- package/dist/evals/routing-corpus.d.ts +35 -0
- package/dist/evals/routing-corpus.d.ts.map +1 -0
- package/dist/evals/routing-corpus.js +142 -0
- package/dist/evals/routing-corpus.js.map +1 -0
- package/dist/evals/routing-eval.d.ts +104 -0
- package/dist/evals/routing-eval.d.ts.map +1 -0
- package/dist/evals/routing-eval.js +231 -0
- package/dist/evals/routing-eval.js.map +1 -0
- package/dist/evals/tfidf.d.ts +35 -0
- package/dist/evals/tfidf.d.ts.map +1 -0
- package/dist/evals/tfidf.js +132 -0
- package/dist/evals/tfidf.js.map +1 -0
- package/dist/review/prepass.d.ts +88 -0
- package/dist/review/prepass.d.ts.map +1 -0
- package/dist/review/prepass.js +428 -0
- package/dist/review/prepass.js.map +1 -0
- package/dist/review/visual-captures.d.ts +60 -0
- package/dist/review/visual-captures.d.ts.map +1 -0
- package/dist/review/visual-captures.js +222 -0
- package/dist/review/visual-captures.js.map +1 -0
- package/dist/supervise/assess.d.ts +73 -0
- package/dist/supervise/assess.d.ts.map +1 -0
- package/dist/supervise/assess.js +215 -0
- package/dist/supervise/assess.js.map +1 -0
- package/dist/supervise/config.d.ts +13 -0
- package/dist/supervise/config.d.ts.map +1 -0
- package/dist/supervise/config.js +81 -0
- package/dist/supervise/config.js.map +1 -0
- package/dist/supervise/loop.d.ts +78 -0
- package/dist/supervise/loop.d.ts.map +1 -0
- package/dist/supervise/loop.js +311 -0
- package/dist/supervise/loop.js.map +1 -0
- package/dist/supervise/observe.d.ts +31 -0
- package/dist/supervise/observe.d.ts.map +1 -0
- package/dist/supervise/observe.js +172 -0
- package/dist/supervise/observe.js.map +1 -0
- package/dist/supervise/policy.d.ts +16 -0
- package/dist/supervise/policy.d.ts.map +1 -0
- package/dist/supervise/policy.js +147 -0
- package/dist/supervise/policy.js.map +1 -0
- package/dist/supervise/types.d.ts +109 -0
- package/dist/supervise/types.d.ts.map +1 -0
- package/dist/supervise/types.js +11 -0
- package/dist/supervise/types.js.map +1 -0
- package/docs/INDEX.md +15 -4
- package/docs/architecture/adr/0001-automode-risk-floor.md +75 -0
- package/docs/architecture/adr/0002-evidence-bound-ship.md +81 -0
- package/docs/guides/CAPACITY_POLICY.md +99 -0
- package/docs/performance/automode-risk-calibration.md +72 -0
- package/docs/performance/reliability-ladder.md +78 -0
- package/docs/plans/system1-uplift-2026-09-18.md +190 -0
- package/docs/reference/automode-risk.md +134 -0
- package/docs/reference/evidence-bound-ship.md +146 -0
- package/docs/reference/review-prepass.md +77 -0
- package/docs/reference/routing-evals.md +71 -0
- package/docs/reference/semantic-supervisor.md +140 -0
- package/docs/reference/system1-classifier.md +103 -0
- package/docs/reference/visual-captures.md +113 -0
- package/package.json +5 -3
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
- package/src/policies/enforcers/enforcement_self_protect.py +25 -2
- package/src/policies/enforcers/evidence_bound_ship.py +257 -0
- package/src/policies/enforcers/expert_review_required.py +120 -0
- package/src/policies/schemas/policies/evidence-bound-ship.md +106 -0
- package/src/policies/schemas/policies/visual-verification.md +8 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +782 -19
- package/tools/agents/scripts/project_telemetry.py +52 -0
- package/tools/agents/scripts/tool_risk.py +789 -0
- package/tools/agents/scripts/tool_risk_calibrate.py +255 -0
- package/tools/agents/tests/fixtures/tool_risk_cases.json +124 -0
- package/tools/agents/tests/test_enforcer_escape_hatches.py +13 -0
- package/tools/agents/tests/test_gate_evidence.py +56 -0
- package/tools/agents/tests/test_tool_risk.py +521 -0
- package/tools/agents/tests/test_verbatim_compact.py +629 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Verbatim decision compaction (uplift 1.2).
|
|
3
|
+
|
|
4
|
+
The proxy's context-pressure path used to drop a contiguous oldest-middle
|
|
5
|
+
block and replace it with a one-line breadcrumb summary — LOSSY: dropped
|
|
6
|
+
findings evaporate. `verbatim_compact_conversation` sits AHEAD of that path
|
|
7
|
+
and makes per-tool-call keep/truncate/drop decisions:
|
|
8
|
+
|
|
9
|
+
stage 1: drop pure-noise tool pairs (acks, superseded reads, repeats)
|
|
10
|
+
stage 2: truncate large kept tool results in place (verbatim head + note)
|
|
11
|
+
stage 3: return None -> fall back to the contiguous breadcrumb pruner
|
|
12
|
+
|
|
13
|
+
Hard invariants pinned here:
|
|
14
|
+
- kept content is VERBATIM (no summarization of retained messages);
|
|
15
|
+
- PAIRING INTEGRITY: a tool_use and its tool_result are an atomic pair —
|
|
16
|
+
no orphan tool_result, no dangling tool_use; any integrity failure or
|
|
17
|
+
insufficient reduction fails CLOSED to the old path (returns None);
|
|
18
|
+
- PROXY_VERBATIM_COMPACT=off restores the old path entirely;
|
|
19
|
+
- on a noise-bearing oversized fixture the verbatim path reduces at least
|
|
20
|
+
as much as the lossy pruner.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import copy
|
|
24
|
+
import importlib.util
|
|
25
|
+
import unittest
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
|
|
29
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
30
|
+
ap = importlib.util.module_from_spec(spec)
|
|
31
|
+
spec.loader.exec_module(ap)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
BIG = 1400 # chars per large tool result (~400 tokens at 3.5 chars/token)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _pad(prefix, char):
|
|
38
|
+
return prefix + char * max(0, BIG - len(prefix))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _assistant_call(idx, name, tool_input, text=None):
|
|
42
|
+
blocks = []
|
|
43
|
+
if text:
|
|
44
|
+
blocks.append({"type": "text", "text": text})
|
|
45
|
+
blocks.append(
|
|
46
|
+
{"type": "tool_use", "id": f"toolu_{idx}", "name": name, "input": tool_input}
|
|
47
|
+
)
|
|
48
|
+
return {"role": "assistant", "content": blocks}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _user_result(idx, text):
|
|
52
|
+
return {
|
|
53
|
+
"role": "user",
|
|
54
|
+
"content": [
|
|
55
|
+
{"type": "tool_result", "tool_use_id": f"toolu_{idx}", "content": text}
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _pair(idx, name, tool_input, result_text, assistant_text=None):
|
|
61
|
+
return [
|
|
62
|
+
_assistant_call(idx, name, tool_input, text=assistant_text),
|
|
63
|
+
_user_result(idx, result_text),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _noise_fixture():
|
|
68
|
+
"""Oversized conversation whose middle mixes pure-noise pairs with
|
|
69
|
+
unique signal pairs. Window 8000 x 0.50 -> 4000-token message budget;
|
|
70
|
+
the middle alone is ~4800 tokens, ~45% of it pure noise, so stage 1
|
|
71
|
+
alone brings the conversation under budget without stage-2 truncation."""
|
|
72
|
+
messages = [{"role": "user", "content": "recon: map the auth flow"}]
|
|
73
|
+
idx = 0
|
|
74
|
+
|
|
75
|
+
def add(name, tool_input, result_text):
|
|
76
|
+
nonlocal idx
|
|
77
|
+
messages.extend(_pair(idx, name, tool_input, result_text))
|
|
78
|
+
idx += 1
|
|
79
|
+
|
|
80
|
+
add("Read", {"file_path": "/src/dupC.py"}, _pad("DUPC-OLD ", "e")) # superseded below
|
|
81
|
+
add("Read", {"file_path": "/src/dupA.py"}, _pad("DUPA-OLD ", "a")) # superseded below
|
|
82
|
+
add("Write", {"file_path": "/src/out1.py"},
|
|
83
|
+
"File created successfully at: /src/out1.py") # ack (toolu_2)
|
|
84
|
+
add("Read", {"file_path": "/src/sig1.py"}, _pad("SIG1 ", "1")) # signal
|
|
85
|
+
add("Bash", {"command": "git status"},
|
|
86
|
+
"On branch master\nnothing to commit, working tree clean") # repeat below
|
|
87
|
+
add("Read", {"file_path": "/src/dupB.py"}, _pad("DUPB-OLD ", "b")) # superseded below
|
|
88
|
+
add("Read", {"file_path": "/src/sig2.py"}, _pad("SIG2 ", "2")) # signal
|
|
89
|
+
add("Read", {"file_path": "/src/dupA.py"}, _pad("DUPA-NEW ", "c")) # freshest A
|
|
90
|
+
add("Write", {"file_path": "/src/out2.py"},
|
|
91
|
+
"File created successfully at: /src/out2.py") # ack
|
|
92
|
+
add("Read", {"file_path": "/src/sig3.py"}, _pad("SIG3 ", "3")) # signal
|
|
93
|
+
add("Bash", {"command": "git status"},
|
|
94
|
+
"On branch master\nnothing to commit, working tree clean") # freshest repeat
|
|
95
|
+
add("Read", {"file_path": "/src/dupB.py"}, _pad("DUPB-NEW ", "d")) # freshest B
|
|
96
|
+
add("Read", {"file_path": "/src/dupD.py"}, _pad("DUPD-OLD ", "g")) # superseded below
|
|
97
|
+
add("Read", {"file_path": "/src/dupD.py"}, _pad("DUPD-NEW ", "h")) # freshest D
|
|
98
|
+
add("Read", {"file_path": "/src/dupC.py"}, _pad("DUPC-NEW ", "f")) # freshest C
|
|
99
|
+
add("Read", {"file_path": "/src/sig4.py"}, _pad("SIG4 ", "4")) # signal
|
|
100
|
+
for tail in ("t0", "t1", "t2", "t3"):
|
|
101
|
+
messages.append({"role": "user", "content": tail})
|
|
102
|
+
return {"messages": messages}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _big_signal_fixture(n_pairs=6, result_chars=3000):
|
|
106
|
+
"""Oversized conversation with NO noise: every pair is a unique read of
|
|
107
|
+
a unique path with a large unique result."""
|
|
108
|
+
messages = [{"role": "user", "content": "analyze everything"}]
|
|
109
|
+
for i in range(n_pairs):
|
|
110
|
+
messages.extend(
|
|
111
|
+
_pair(
|
|
112
|
+
i,
|
|
113
|
+
"Read",
|
|
114
|
+
{"file_path": f"/src/uniq{i}.py"},
|
|
115
|
+
f"HEAD{i} " + (f"u{i}" * (result_chars // 2)) + f" TAIL{i}",
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
for tail in ("t0", "t1", "t2", "t3"):
|
|
119
|
+
messages.append({"role": "user", "content": tail})
|
|
120
|
+
return {"messages": messages}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _all_text(body):
|
|
124
|
+
out = []
|
|
125
|
+
for msg in body["messages"]:
|
|
126
|
+
content = msg.get("content", "")
|
|
127
|
+
if isinstance(content, str):
|
|
128
|
+
out.append(content)
|
|
129
|
+
elif isinstance(content, list):
|
|
130
|
+
for block in content:
|
|
131
|
+
if isinstance(block, dict):
|
|
132
|
+
if block.get("type") == "text":
|
|
133
|
+
out.append(block.get("text", ""))
|
|
134
|
+
elif block.get("type") == "tool_result":
|
|
135
|
+
out.append(ap._extract_text(block.get("content", "")))
|
|
136
|
+
return "\n".join(out)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TestStage1KeepDropDecisions(unittest.TestCase):
|
|
140
|
+
def test_drops_pure_noise_keeps_signal_verbatim(self):
|
|
141
|
+
body = _noise_fixture()
|
|
142
|
+
original_total = ap.estimate_total_tokens(body)
|
|
143
|
+
out = ap.verbatim_compact_conversation(
|
|
144
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
145
|
+
)
|
|
146
|
+
self.assertIsNotNone(out)
|
|
147
|
+
blob = _all_text(out)
|
|
148
|
+
# noise gone: superseded first reads, ack writes, the repeated call
|
|
149
|
+
self.assertNotIn("DUPA-OLD", blob)
|
|
150
|
+
self.assertNotIn("DUPB-OLD", blob)
|
|
151
|
+
self.assertNotIn("DUPC-OLD", blob)
|
|
152
|
+
self.assertNotIn("DUPD-OLD", blob)
|
|
153
|
+
self.assertNotIn("File created successfully", blob)
|
|
154
|
+
self.assertEqual(blob.count("working tree clean"), 1) # freshest kept
|
|
155
|
+
# signal kept VERBATIM — the full untruncated result text
|
|
156
|
+
self.assertIn(_pad("SIG1 ", "1"), blob)
|
|
157
|
+
self.assertIn(_pad("SIG4 ", "4"), blob)
|
|
158
|
+
self.assertIn(_pad("DUPA-NEW ", "c"), blob)
|
|
159
|
+
self.assertIn(_pad("DUPD-NEW ", "h"), blob)
|
|
160
|
+
self.assertIn(_pad("DUPC-NEW ", "f"), blob)
|
|
161
|
+
# no breadcrumb summary anywhere — verbatim path never summarizes
|
|
162
|
+
self.assertNotIn("CONTEXT PRUNED", blob)
|
|
163
|
+
# stage 1 sufficed — nothing was truncated
|
|
164
|
+
self.assertNotIn("[… truncated ~", blob)
|
|
165
|
+
# head and tail pinned
|
|
166
|
+
self.assertEqual(out["messages"][0]["content"], "recon: map the auth flow")
|
|
167
|
+
self.assertEqual([m["content"] for m in out["messages"][-4:]], ["t0", "t1", "t2", "t3"])
|
|
168
|
+
# actually reduced
|
|
169
|
+
self.assertLess(ap.estimate_total_tokens(out), original_total)
|
|
170
|
+
|
|
171
|
+
def test_ack_pair_with_meaningful_prose_is_kept(self):
|
|
172
|
+
"""When in doubt, keep: an ack result riding on an assistant message
|
|
173
|
+
with real prose is not pure noise."""
|
|
174
|
+
body = _noise_fixture()
|
|
175
|
+
# give the first Write pair's (toolu_2) assistant message meaningful text
|
|
176
|
+
for msg in body["messages"]:
|
|
177
|
+
content = msg.get("content", [])
|
|
178
|
+
if isinstance(content, list) and any(
|
|
179
|
+
isinstance(b, dict) and b.get("id") == "toolu_2" for b in content
|
|
180
|
+
):
|
|
181
|
+
content.insert(
|
|
182
|
+
0, {"type": "text", "text": "Now writing the entrypoint module."}
|
|
183
|
+
)
|
|
184
|
+
break
|
|
185
|
+
out = ap.verbatim_compact_conversation(
|
|
186
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
187
|
+
)
|
|
188
|
+
self.assertIsNotNone(out)
|
|
189
|
+
blob = _all_text(out)
|
|
190
|
+
self.assertIn("Now writing the entrypoint module.", blob)
|
|
191
|
+
self.assertIn("File created successfully at: /src/out1.py", blob)
|
|
192
|
+
|
|
193
|
+
def test_survivors_keep_original_relative_order(self):
|
|
194
|
+
"""KV doctrine: never reshuffle — surviving middle messages appear in
|
|
195
|
+
their original order."""
|
|
196
|
+
body = _noise_fixture()
|
|
197
|
+
original = list(body["messages"])
|
|
198
|
+
out = ap.verbatim_compact_conversation(
|
|
199
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
200
|
+
)
|
|
201
|
+
self.assertIsNotNone(out)
|
|
202
|
+
idxs = [
|
|
203
|
+
i
|
|
204
|
+
for msg in out["messages"]
|
|
205
|
+
for i, orig in enumerate(original)
|
|
206
|
+
if msg is not orig and msg == orig
|
|
207
|
+
]
|
|
208
|
+
self.assertEqual(idxs, sorted(idxs))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class TestStage2Truncation(unittest.TestCase):
|
|
212
|
+
def test_truncates_oldest_first_keeps_head_verbatim(self):
|
|
213
|
+
body = _big_signal_fixture()
|
|
214
|
+
original_texts = [
|
|
215
|
+
ap._extract_text(m["content"][0].get("content", ""))
|
|
216
|
+
for m in body["messages"]
|
|
217
|
+
if m.get("role") == "user"
|
|
218
|
+
and isinstance(m.get("content"), list)
|
|
219
|
+
]
|
|
220
|
+
out = ap.verbatim_compact_conversation(
|
|
221
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
222
|
+
)
|
|
223
|
+
self.assertIsNotNone(out)
|
|
224
|
+
budget = int(8000 * 0.50)
|
|
225
|
+
total = sum(ap.estimate_message_tokens(m) for m in out["messages"])
|
|
226
|
+
self.assertLessEqual(total, budget)
|
|
227
|
+
|
|
228
|
+
result_texts = [
|
|
229
|
+
ap._extract_text(m["content"][0].get("content", ""))
|
|
230
|
+
for m in out["messages"]
|
|
231
|
+
if m.get("role") == "user" and isinstance(m.get("content"), list)
|
|
232
|
+
]
|
|
233
|
+
truncated = [t for t in result_texts if "[… truncated ~" in t]
|
|
234
|
+
untouched = [t for t in result_texts if "[… truncated ~" not in t]
|
|
235
|
+
self.assertTrue(truncated, "expected at least one truncated result")
|
|
236
|
+
for orig, now in zip(original_texts, result_texts):
|
|
237
|
+
if "[… truncated ~" in now:
|
|
238
|
+
# head kept VERBATIM, then the note
|
|
239
|
+
self.assertTrue(now.startswith(orig[: ap._VERBATIM_TRUNCATE_HEAD_CHARS]))
|
|
240
|
+
self.assertLess(len(now), len(orig))
|
|
241
|
+
else:
|
|
242
|
+
self.assertEqual(now, orig) # untouched stays byte-identical
|
|
243
|
+
# oldest-first: the newest middle results survive untruncated
|
|
244
|
+
self.assertTrue(any(t == original_texts[-1] for t in untouched))
|
|
245
|
+
|
|
246
|
+
def test_truncation_preserves_pairing(self):
|
|
247
|
+
body = _big_signal_fixture()
|
|
248
|
+
out = ap.verbatim_compact_conversation(
|
|
249
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
250
|
+
)
|
|
251
|
+
self.assertIsNotNone(out)
|
|
252
|
+
self.assertTrue(ap._tool_pairing_intact(out["messages"]))
|
|
253
|
+
# every surviving tool_use id still has its tool_result and vice versa
|
|
254
|
+
use_ids = {
|
|
255
|
+
b["id"]
|
|
256
|
+
for m in out["messages"]
|
|
257
|
+
for b in ap._iter_tool_use_blocks(m)
|
|
258
|
+
}
|
|
259
|
+
result_ids = {
|
|
260
|
+
b["tool_use_id"]
|
|
261
|
+
for m in out["messages"]
|
|
262
|
+
for b in ap._iter_tool_result_blocks(m)
|
|
263
|
+
}
|
|
264
|
+
self.assertEqual(use_ids, result_ids)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class TestPairingIntegrityValidation(unittest.TestCase):
|
|
268
|
+
def test_valid_paired_conversation(self):
|
|
269
|
+
body = _noise_fixture()
|
|
270
|
+
self.assertTrue(ap._tool_pairing_intact(body["messages"]))
|
|
271
|
+
|
|
272
|
+
def test_orphan_tool_result_rejected(self):
|
|
273
|
+
messages = [
|
|
274
|
+
{"role": "user", "content": [
|
|
275
|
+
{"type": "tool_result", "tool_use_id": "toolu_x", "content": "data"}]},
|
|
276
|
+
{"role": "user", "content": "next"},
|
|
277
|
+
]
|
|
278
|
+
self.assertFalse(ap._tool_pairing_intact(messages))
|
|
279
|
+
|
|
280
|
+
def test_dangling_tool_use_mid_conversation_rejected(self):
|
|
281
|
+
messages = [
|
|
282
|
+
{"role": "user", "content": "go"},
|
|
283
|
+
_assistant_call(0, "Read", {"file_path": "/a"}),
|
|
284
|
+
{"role": "user", "content": "no result for that call"},
|
|
285
|
+
]
|
|
286
|
+
self.assertFalse(ap._tool_pairing_intact(messages))
|
|
287
|
+
|
|
288
|
+
def test_final_assistant_prefill_is_allowed(self):
|
|
289
|
+
messages = [
|
|
290
|
+
{"role": "user", "content": "go"},
|
|
291
|
+
_assistant_call(0, "Read", {"file_path": "/a"}),
|
|
292
|
+
]
|
|
293
|
+
self.assertTrue(ap._tool_pairing_intact(messages))
|
|
294
|
+
|
|
295
|
+
def test_partial_result_coverage_rejected(self):
|
|
296
|
+
pair = _assistant_call(0, "Read", {"file_path": "/a"})
|
|
297
|
+
pair["content"].append(
|
|
298
|
+
{"type": "tool_use", "id": "toolu_99", "name": "Read",
|
|
299
|
+
"input": {"file_path": "/b"}}
|
|
300
|
+
)
|
|
301
|
+
messages = [
|
|
302
|
+
{"role": "user", "content": "go"},
|
|
303
|
+
pair,
|
|
304
|
+
_user_result(0, "only one of two answered"),
|
|
305
|
+
]
|
|
306
|
+
self.assertFalse(ap._tool_pairing_intact(messages))
|
|
307
|
+
|
|
308
|
+
def test_unfixable_input_pairing_fails_closed(self):
|
|
309
|
+
"""A conversation whose pairing is ALREADY broken (orphan result) can
|
|
310
|
+
be stage-1 reduced but never validated — the verbatim path must fail
|
|
311
|
+
closed (None) rather than ship the broken pairing."""
|
|
312
|
+
body = _noise_fixture()
|
|
313
|
+
# inject an orphan tool_result into the middle
|
|
314
|
+
body["messages"].insert(
|
|
315
|
+
5,
|
|
316
|
+
{"role": "user", "content": [
|
|
317
|
+
{"type": "tool_result", "tool_use_id": "toolu_orphan",
|
|
318
|
+
"content": "orphaned"}]},
|
|
319
|
+
)
|
|
320
|
+
out = ap.verbatim_compact_conversation(
|
|
321
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
322
|
+
)
|
|
323
|
+
self.assertIsNone(out)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class TestFallbacks(unittest.TestCase):
|
|
327
|
+
def test_none_when_stages_cannot_reach_budget(self):
|
|
328
|
+
"""All-signal conversation far over budget: even truncating every
|
|
329
|
+
result to its head cannot fit — stage 3 fails closed."""
|
|
330
|
+
body = _big_signal_fixture(n_pairs=8, result_chars=3000)
|
|
331
|
+
out = ap.verbatim_compact_conversation(
|
|
332
|
+
copy.deepcopy(body), 4000, target_fraction=0.25, keep_last=4
|
|
333
|
+
)
|
|
334
|
+
self.assertIsNone(out)
|
|
335
|
+
|
|
336
|
+
def test_none_when_nothing_to_do(self):
|
|
337
|
+
body = _big_signal_fixture(n_pairs=2, result_chars=200)
|
|
338
|
+
out = ap.verbatim_compact_conversation(
|
|
339
|
+
copy.deepcopy(body), 128000, target_fraction=0.65, keep_last=4
|
|
340
|
+
)
|
|
341
|
+
self.assertIsNone(out)
|
|
342
|
+
|
|
343
|
+
def test_none_on_few_message_body(self):
|
|
344
|
+
body = {"messages": [{"role": "user", "content": "x" * 900000}]}
|
|
345
|
+
self.assertIsNone(
|
|
346
|
+
ap.verbatim_compact_conversation(body, 100000, target_fraction=0.5)
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
def test_callers_body_is_pristine_after_fallback(self):
|
|
350
|
+
"""The verbatim path works on a copy: a fallback must leave the
|
|
351
|
+
original body untouched for the lossy pruner."""
|
|
352
|
+
body = _big_signal_fixture(n_pairs=8, result_chars=3000)
|
|
353
|
+
snapshot = copy.deepcopy(body)
|
|
354
|
+
out = ap.verbatim_compact_conversation(
|
|
355
|
+
body, 4000, target_fraction=0.25, keep_last=4
|
|
356
|
+
)
|
|
357
|
+
self.assertIsNone(out)
|
|
358
|
+
self.assertEqual(body, snapshot)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class TestEscapeHatchAndWiring(unittest.TestCase):
|
|
362
|
+
def test_env_off_restores_old_path_entirely(self):
|
|
363
|
+
body = _noise_fixture()
|
|
364
|
+
expected = ap.prune_conversation(
|
|
365
|
+
copy.deepcopy(body), 8000,
|
|
366
|
+
monitor=ap.SessionMonitor(context_window=8000),
|
|
367
|
+
target_fraction=0.50, keep_last=4,
|
|
368
|
+
)
|
|
369
|
+
old = ap.PROXY_VERBATIM_COMPACT
|
|
370
|
+
try:
|
|
371
|
+
ap.PROXY_VERBATIM_COMPACT = "off"
|
|
372
|
+
got = ap._compact_conversation(
|
|
373
|
+
copy.deepcopy(body), 8000,
|
|
374
|
+
monitor=ap.SessionMonitor(context_window=8000),
|
|
375
|
+
target_fraction=0.50, keep_last=4,
|
|
376
|
+
)
|
|
377
|
+
finally:
|
|
378
|
+
ap.PROXY_VERBATIM_COMPACT = old
|
|
379
|
+
self.assertEqual(got["messages"], expected["messages"])
|
|
380
|
+
self.assertIn("CONTEXT PRUNED", _all_text(got))
|
|
381
|
+
|
|
382
|
+
def test_wiring_prefers_verbatim_and_skips_drop_boundary(self):
|
|
383
|
+
"""With compaction on, the wrapper returns the verbatim result and
|
|
384
|
+
does NOT advance the fallback's monotonic prune_drop_count."""
|
|
385
|
+
monitor = ap.SessionMonitor(context_window=8000)
|
|
386
|
+
out = ap._compact_conversation(
|
|
387
|
+
_noise_fixture(), 8000,
|
|
388
|
+
monitor=monitor, target_fraction=0.50, keep_last=4,
|
|
389
|
+
)
|
|
390
|
+
blob = _all_text(out)
|
|
391
|
+
self.assertNotIn("CONTEXT PRUNED", blob)
|
|
392
|
+
self.assertIn(_pad("SIG1 ", "1"), blob)
|
|
393
|
+
self.assertEqual(monitor.prune_drop_count, 0)
|
|
394
|
+
|
|
395
|
+
def test_wiring_fails_closed_on_exception(self):
|
|
396
|
+
"""ANY exception in the verbatim path falls back to the known-good
|
|
397
|
+
pruner with the caller's body intact."""
|
|
398
|
+
old_fn = ap.verbatim_compact_conversation
|
|
399
|
+
|
|
400
|
+
def _boom(*args, **kwargs):
|
|
401
|
+
raise RuntimeError("classifier exploded")
|
|
402
|
+
|
|
403
|
+
ap.verbatim_compact_conversation = _boom
|
|
404
|
+
try:
|
|
405
|
+
out = ap._compact_conversation(
|
|
406
|
+
_noise_fixture(), 8000,
|
|
407
|
+
monitor=ap.SessionMonitor(context_window=8000),
|
|
408
|
+
target_fraction=0.50, keep_last=4,
|
|
409
|
+
)
|
|
410
|
+
finally:
|
|
411
|
+
ap.verbatim_compact_conversation = old_fn
|
|
412
|
+
self.assertIn("CONTEXT PRUNED", _all_text(out))
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class TestReductionComparison(unittest.TestCase):
|
|
416
|
+
def test_verbatim_reduces_at_least_as_much_as_lossy_prune(self):
|
|
417
|
+
"""On the same oversized noise-bearing fixture, the verbatim path's
|
|
418
|
+
context reduction is >= the old pruner's — it removes only noise and
|
|
419
|
+
adds no breadcrumb/carryover marker back."""
|
|
420
|
+
body = _noise_fixture()
|
|
421
|
+
before = ap.estimate_total_tokens(body)
|
|
422
|
+
verbatim = ap.verbatim_compact_conversation(
|
|
423
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
424
|
+
)
|
|
425
|
+
lossy = ap.prune_conversation(
|
|
426
|
+
copy.deepcopy(body), 8000,
|
|
427
|
+
monitor=ap.SessionMonitor(context_window=8000),
|
|
428
|
+
target_fraction=0.50, keep_last=4,
|
|
429
|
+
)
|
|
430
|
+
self.assertIsNotNone(verbatim)
|
|
431
|
+
v_reduction = before - ap.estimate_total_tokens(verbatim)
|
|
432
|
+
l_reduction = before - ap.estimate_total_tokens(lossy)
|
|
433
|
+
self.assertGreaterEqual(v_reduction, l_reduction)
|
|
434
|
+
# and the verbatim result fits the same budget the pruner targets
|
|
435
|
+
budget = int(8000 * 0.50)
|
|
436
|
+
self.assertLessEqual(
|
|
437
|
+
sum(ap.estimate_message_tokens(m) for m in verbatim["messages"]),
|
|
438
|
+
budget,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _custom_fixture(middle_messages):
|
|
443
|
+
"""Head + given middle + 4-message tail, matching the keep_last=4 shape."""
|
|
444
|
+
return {
|
|
445
|
+
"messages": [{"role": "user", "content": "task"}]
|
|
446
|
+
+ middle_messages
|
|
447
|
+
+ [{"role": "user", "content": t} for t in ("t0", "t1", "t2", "t3")]
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _padding_pairs(start_idx, n, result_chars=3000):
|
|
452
|
+
"""n unique signal Read pairs (never noise) to push a fixture over budget."""
|
|
453
|
+
msgs = []
|
|
454
|
+
for i in range(start_idx, start_idx + n):
|
|
455
|
+
msgs.extend(
|
|
456
|
+
_pair(i, "Read", {"file_path": f"/src/pad{i}.py"},
|
|
457
|
+
f"PAD{i} " + (f"p{i}" * (result_chars // 2)))
|
|
458
|
+
)
|
|
459
|
+
return msgs
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
class TestTruncateBandRegression(unittest.TestCase):
|
|
463
|
+
def test_1300_char_band_block_untouched(self):
|
|
464
|
+
"""A text in the (MIN=1200, HEAD=1500 + note] band would GROW by the
|
|
465
|
+
note with nothing cut — it must be skipped entirely."""
|
|
466
|
+
pair = _pair(0, "Read", {"file_path": "/a"}, "B" * 1300)
|
|
467
|
+
removed = ap._truncate_pair(pair)
|
|
468
|
+
self.assertEqual(removed, 0)
|
|
469
|
+
self.assertEqual(pair[1]["content"][0]["content"], "B" * 1300)
|
|
470
|
+
|
|
471
|
+
def test_truncate_text_block_band_boundaries(self):
|
|
472
|
+
self.assertEqual(ap._truncate_text_block("x" * 1300), ("x" * 1300, 0))
|
|
473
|
+
big = "y" * 5000
|
|
474
|
+
new_text, removed = ap._truncate_text_block(big)
|
|
475
|
+
self.assertGreater(removed, 0)
|
|
476
|
+
self.assertTrue(new_text.startswith("y" * ap._VERBATIM_TRUNCATE_HEAD_CHARS))
|
|
477
|
+
self.assertIn("[… truncated ~", new_text)
|
|
478
|
+
self.assertLess(len(new_text), len(big))
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
class TestRepeatedCallResultEquality(unittest.TestCase):
|
|
482
|
+
def test_earlier_error_result_is_not_dropped(self):
|
|
483
|
+
"""A repeated call whose EARLIER result was an error must not vanish
|
|
484
|
+
because the later identical call succeeded blandly."""
|
|
485
|
+
middle = _pair(0, "Bash", {"command": "probe"}, "Error: connection refused")
|
|
486
|
+
middle[1]["content"][0]["is_error"] = True
|
|
487
|
+
middle += _pair(1, "Bash", {"command": "probe"}, "probe ok")
|
|
488
|
+
middle += _padding_pairs(10, 6)
|
|
489
|
+
body = _custom_fixture(middle)
|
|
490
|
+
out = ap.verbatim_compact_conversation(
|
|
491
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
492
|
+
)
|
|
493
|
+
self.assertIsNotNone(out)
|
|
494
|
+
self.assertIn("Error: connection refused", _all_text(out))
|
|
495
|
+
|
|
496
|
+
def test_identical_bland_repeat_is_dropped(self):
|
|
497
|
+
middle = _pair(0, "Bash", {"command": "probe"}, "probe ok")
|
|
498
|
+
middle += _pair(1, "Bash", {"command": "probe"}, "probe ok")
|
|
499
|
+
middle += _padding_pairs(10, 6)
|
|
500
|
+
body = _custom_fixture(middle)
|
|
501
|
+
out = ap.verbatim_compact_conversation(
|
|
502
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
503
|
+
)
|
|
504
|
+
self.assertIsNotNone(out)
|
|
505
|
+
self.assertEqual(_all_text(out).count("probe ok"), 1) # freshest kept
|
|
506
|
+
|
|
507
|
+
def test_ack_phrase_with_real_payload_is_not_ack(self):
|
|
508
|
+
self.assertFalse(ap._is_ack_result("ok, here are the 3 failing tests"))
|
|
509
|
+
self.assertTrue(ap._is_ack_result("OK."))
|
|
510
|
+
self.assertTrue(
|
|
511
|
+
ap._is_ack_result("File created successfully at: /src/out.py")
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class TestDropBreadcrumb(unittest.TestCase):
|
|
516
|
+
def test_breadcrumb_present_bounded_and_lists_tools(self):
|
|
517
|
+
body = _noise_fixture()
|
|
518
|
+
out = ap.verbatim_compact_conversation(
|
|
519
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
520
|
+
)
|
|
521
|
+
self.assertIsNotNone(out)
|
|
522
|
+
crumbs = [
|
|
523
|
+
m["content"]
|
|
524
|
+
for m in out["messages"]
|
|
525
|
+
if isinstance(m.get("content"), str) and m["content"].startswith("[compaction:")
|
|
526
|
+
]
|
|
527
|
+
self.assertEqual(len(crumbs), 1) # exactly one note, at the first drop
|
|
528
|
+
crumb = crumbs[0]
|
|
529
|
+
self.assertLessEqual(len(crumb), 300)
|
|
530
|
+
self.assertIn("noise tool pairs elided", crumb)
|
|
531
|
+
self.assertIn("read /src/", crumb) # names tools + paths
|
|
532
|
+
self.assertIn("write /src/", crumb)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class TestSupersededReadDoctrine(unittest.TestCase):
|
|
536
|
+
def test_intervening_write_keeps_first_read(self):
|
|
537
|
+
"""Read A -> Write A -> Read A: the first read is the before/after
|
|
538
|
+
record — NOT superseded."""
|
|
539
|
+
middle = _pair(0, "Read", {"file_path": "/src/state.py"}, _pad("OLD-A ", "o"))
|
|
540
|
+
middle += _pair(1, "Write", {"file_path": "/src/state.py"},
|
|
541
|
+
"File created successfully at: /src/state.py")
|
|
542
|
+
middle += _pair(2, "Read", {"file_path": "/src/state.py"}, _pad("NEW-A ", "n"))
|
|
543
|
+
middle += _padding_pairs(10, 6)
|
|
544
|
+
body = _custom_fixture(middle)
|
|
545
|
+
out = ap.verbatim_compact_conversation(
|
|
546
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
547
|
+
)
|
|
548
|
+
self.assertIsNotNone(out)
|
|
549
|
+
blob = _all_text(out)
|
|
550
|
+
self.assertIn(_pad("OLD-A ", "o"), blob) # before-record kept
|
|
551
|
+
self.assertIn(_pad("NEW-A ", "n"), blob)
|
|
552
|
+
|
|
553
|
+
def test_later_errored_read_does_not_supersede(self):
|
|
554
|
+
middle = _pair(0, "Read", {"file_path": "/src/x.py"}, _pad("GOOD-READ ", "g"))
|
|
555
|
+
middle += _pair(1, "Read", {"file_path": "/src/x.py"}, "Error: boom")
|
|
556
|
+
middle[3]["content"][0]["is_error"] = True # second read errored
|
|
557
|
+
middle += _padding_pairs(10, 6)
|
|
558
|
+
body = _custom_fixture(middle)
|
|
559
|
+
out = ap.verbatim_compact_conversation(
|
|
560
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
561
|
+
)
|
|
562
|
+
self.assertIsNotNone(out)
|
|
563
|
+
self.assertIn(_pad("GOOD-READ ", "g"), _all_text(out))
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class TestStage2WritePayloadImmunity(unittest.TestCase):
|
|
567
|
+
def test_write_pair_input_survives_stage2_verbatim(self):
|
|
568
|
+
"""Stage 2 truncates the Write pair's RESULT but never its input —
|
|
569
|
+
the input payload is the model's own action history."""
|
|
570
|
+
write_input = {"file_path": "/src/big.py", "content": "W" * 3000}
|
|
571
|
+
middle = _pair(0, "Write", write_input, "WRITE-RESULT " + "r" * 3000)
|
|
572
|
+
middle += _padding_pairs(10, 5)
|
|
573
|
+
body = _custom_fixture(middle)
|
|
574
|
+
out = ap.verbatim_compact_conversation(
|
|
575
|
+
copy.deepcopy(body), 8000, target_fraction=0.50, keep_last=4
|
|
576
|
+
)
|
|
577
|
+
self.assertIsNotNone(out)
|
|
578
|
+
write_use = next(
|
|
579
|
+
b
|
|
580
|
+
for m in out["messages"]
|
|
581
|
+
for b in ap._iter_tool_use_blocks(m)
|
|
582
|
+
if b.get("name") == "Write"
|
|
583
|
+
)
|
|
584
|
+
self.assertEqual(write_use["input"]["content"], "W" * 3000) # intact
|
|
585
|
+
write_result = next(
|
|
586
|
+
b
|
|
587
|
+
for m in out["messages"]
|
|
588
|
+
for b in ap._iter_tool_result_blocks(m)
|
|
589
|
+
if b.get("tool_use_id") == "toolu_0"
|
|
590
|
+
)
|
|
591
|
+
self.assertIn("[… truncated ~", ap._extract_text(write_result["content"]))
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class TestMonitorBoundaryReset(unittest.TestCase):
|
|
595
|
+
def test_prune_drop_count_reset_on_verbatim_success(self):
|
|
596
|
+
"""The verbatim path re-indexes the middle — a stale legacy boundary
|
|
597
|
+
would force-drop kept signal on a later fallback turn."""
|
|
598
|
+
monitor = ap.SessionMonitor(context_window=8000)
|
|
599
|
+
monitor.prune_drop_count = 7
|
|
600
|
+
out = ap.verbatim_compact_conversation(
|
|
601
|
+
_noise_fixture(), 8000, monitor=monitor,
|
|
602
|
+
target_fraction=0.50, keep_last=4,
|
|
603
|
+
)
|
|
604
|
+
self.assertIsNotNone(out)
|
|
605
|
+
self.assertEqual(monitor.prune_drop_count, 0)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
class TestWideFixturePerformance(unittest.TestCase):
|
|
609
|
+
def test_2000_pair_fixture_completes_fast(self):
|
|
610
|
+
"""Guards the occurrence-index complexity: 1000 unique calls, each
|
|
611
|
+
repeated once (all stage-1 noise), must not go quadratic."""
|
|
612
|
+
import time
|
|
613
|
+
|
|
614
|
+
middle = []
|
|
615
|
+
for i in range(1000):
|
|
616
|
+
middle += _pair(2 * i, "Bash", {"command": f"cmd{i}"}, f"out{i} bland")
|
|
617
|
+
middle += _pair(2 * i + 1, "Bash", {"command": f"cmd{i}"}, f"out{i} bland")
|
|
618
|
+
body = _custom_fixture(middle)
|
|
619
|
+
start = time.monotonic()
|
|
620
|
+
out = ap.verbatim_compact_conversation(
|
|
621
|
+
copy.deepcopy(body), 40000, target_fraction=0.50, keep_last=4
|
|
622
|
+
)
|
|
623
|
+
elapsed = time.monotonic() - start
|
|
624
|
+
self.assertIsNotNone(out) # stage 1 drops the 1000 older repeats
|
|
625
|
+
self.assertLess(elapsed, 5.0)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
if __name__ == "__main__":
|
|
629
|
+
unittest.main()
|