@miller-tech/uap 2.16.6 → 2.16.7

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 CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Tests](https://img.shields.io/badge/tests-450%2B_suites-brightgreen)](#testing)
10
10
  [![License](https://img.shields.io/badge/license-MIT-black)](LICENSE)
11
11
 
12
- `v2.11.0` · 367 TypeScript modules across 26 subsystems · 459 vitest suites (+ a ~1,200-test Python enforcer/proxy suite) · 9 agent harnesses
12
+ `v2.16.6` · 367 TypeScript modules across 26 subsystems · 459 vitest suites (+ a ~1,200-test Python enforcer/proxy suite) · 9 agent harnesses
13
13
 
14
14
  [Quickstart](#quickstart) · [Why UAP?](#why-uap) · [The delivery pipeline](docs/guides/DELIVERY_PIPELINE.md) · [`uap deliver`](#the-deliver-harness) · [Docs](docs/INDEX.md)
15
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "2.16.6",
3
+ "version": "2.16.7",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -2494,6 +2494,15 @@ class SessionMonitor:
2494
2494
  # back into a structured tool_use, which would continue the very loop the
2495
2495
  # breaker is ending. Reset to False at the start of every request.
2496
2496
  suppress_text_tool_extraction: bool = False
2497
+ # Bounded snippet of the most recent tool_result text seen in the request.
2498
+ # Feeds the STUCK-BREAK repeat-call directive so the model is SHOWN the
2499
+ # unchanged output instead of only being told it "already has" it — live
2500
+ # 2026-09-29, a model re-ran `uap memory query "plane workspace slug pay2u"`
2501
+ # 7 times ignoring the directive, because a "No results" output is not an
2502
+ # answer a weak model recognises as final; it believes the missing value
2503
+ # must exist and keeps re-asking. Quoting the output makes the futility
2504
+ # concrete. Refreshed on every request from the conversation.
2505
+ last_tool_result_snippet: str = ""
2497
2506
  # Tool-turn count at which the TURN-COUNT FINALIZE BREAKER last fired. The
2498
2507
  # count is derived from the (only-growing) conversation, so without this the
2499
2508
  # breaker would re-fire on EVERY turn once the ceiling is first crossed —
@@ -7265,15 +7274,27 @@ def _maybe_inject_stuck_break(openai_body: dict, monitor: "SessionMonitor") -> N
7265
7274
  if reason.startswith(_REPEAT_CALL_REASON):
7266
7275
  # The call SUCCEEDS every time, so "stop retrying a failing action" and
7267
7276
  # the switch-channel advice would both be nonsense here. Name the real
7268
- # problem: the answer is already in hand and re-asking cannot change it.
7277
+ # problem: the answer is already in hand and re-asking cannot change
7278
+ # it. QUOTE the actual output — live 2026-09-29, "You already have that
7279
+ # output" was ignored 7 times on a `uap memory query` returning "No
7280
+ # results": the model does not recognise an empty result as an answer,
7281
+ # so the directive must show it that the output IS the output.
7282
+ snippet = (monitor.last_tool_result_snippet or "").strip()
7283
+ quoted = (
7284
+ f' It returns EXACTLY this every time: "{snippet[:300]}"' if snippet else ""
7285
+ )
7269
7286
  directive = (
7270
7287
  "\n\nSTOP — you have issued the same tool call with the same "
7271
7288
  "arguments " + reason.rsplit("x", 1)[-1] + " times in a row. It "
7272
7289
  "SUCCEEDED each time and the result will not change by asking again. "
7273
- "You already have that output. Do NOT repeat it. Either take the "
7274
- "NEXT concrete action using what it told you, or — if you genuinely "
7275
- "cannot proceed — state the single blocking question in one sentence "
7276
- "and stop. Answer in plain text now if the work is done."
7290
+ "You already have that output." + quoted + " Do NOT repeat it — "
7291
+ "re-asking cannot produce new information, and an empty or "
7292
+ "'no results' output IS this source's final answer. Either take the "
7293
+ "NEXT concrete action using what it told you (proceeding WITHOUT "
7294
+ "the missing information and stating your assumption if necessary), "
7295
+ "or — if you genuinely cannot proceed — state the single blocking "
7296
+ "question in one sentence and stop. Answer in plain text now if "
7297
+ "the work is done."
7277
7298
  )
7278
7299
  else:
7279
7300
  directive = (
@@ -9093,6 +9114,9 @@ def _record_last_assistant_tool_calls(
9093
9114
  _latest_err = any(_flags)
9094
9115
  break
9095
9116
  monitor.note_tool_result_error(_latest_tr, _latest_err)
9117
+ # Bounded raw snippet of the same result, for the STUCK-BREAK repeat-call
9118
+ # directive (see SessionMonitor.last_tool_result_snippet).
9119
+ monitor.last_tool_result_snippet = (_latest_tr or "")[:400]
9096
9120
  _seed_tool_history_from_request(monitor, messages)
9097
9121
  tool_fingerprints = []
9098
9122
  tool_targets: dict[str, str] = {}
@@ -226,3 +226,67 @@ class TestStreakSurvivesAFreshMonitor(unittest.TestCase):
226
226
  [{"role": "assistant", "content": "plain text"}]):
227
227
  self.proxy._seed_tool_history_from_request(mon, bad)
228
228
  self.assertEqual(mon.tool_call_history, [])
229
+
230
+
231
+ class TestDirectiveQuotesTheActualOutput(unittest.TestCase):
232
+ """Live 2026-09-29 (ses_f158cb463, qwen3.8): the advisory directive told the
233
+ model 'you already have that output' on a `uap memory query` returning "No
234
+ results" — and it was ignored 7 times, because a weak model does not
235
+ recognise an EMPTY result as an answer; it believes the missing value must
236
+ exist and keeps re-asking. The directive must SHOW the model the output it
237
+ keeps receiving, and say plainly that 'no results' IS the source's answer.
238
+ """
239
+
240
+ @classmethod
241
+ def setUpClass(cls):
242
+ cls.proxy = load_proxy()
243
+
244
+ NO_RESULTS = "No results in short-term memory\n\nNo results in long-term memory"
245
+
246
+ @staticmethod
247
+ def looping_monitor(proxy, snippet):
248
+ mon = proxy.SessionMonitor(context_window=100000)
249
+ for _ in range(4):
250
+ mon.record_tool_calls(tool_names=["Bash"], fingerprint="Bash|uap memory query x")
251
+ mon.last_tool_result_snippet = snippet
252
+ return mon
253
+
254
+ def inject(self, mon):
255
+ body = {"messages": [{"role": "system", "content": "base"}], "tool_choice": "required"}
256
+ self.proxy._maybe_inject_stuck_break(body, mon)
257
+ return body["messages"][0]["content"]
258
+
259
+ def test_the_directive_quotes_the_repeated_calls_actual_output(self):
260
+ text = self.inject(self.looping_monitor(self.proxy, self.NO_RESULTS))
261
+ self.assertIn("No results in short-term memory", text)
262
+ self.assertIn("returns EXACTLY this every time", text)
263
+ # The trap named in words, not just implied by the quote.
264
+ self.assertIn("'no results' output IS this source's final answer", text)
265
+
266
+ def test_a_long_result_is_bounded_in_the_directive(self):
267
+ text = self.inject(self.looping_monitor(self.proxy, "x" * 5000))
268
+ self.assertLess(len(text), 2000, "a huge tool result must not bloat the directive")
269
+
270
+ def test_a_missing_snippet_keeps_the_directive_valid(self):
271
+ # No tool_result seen (e.g. the repeat was in the request seeds only):
272
+ # the directive must still fire, unquoted, and not crash.
273
+ text = self.inject(self.looping_monitor(self.proxy, ""))
274
+ self.assertIn("SUCCEEDED each time", text)
275
+ self.assertIn("will not change", text)
276
+ self.assertNotIn("returns EXACTLY this", text)
277
+
278
+ def test_the_snippet_is_captured_from_the_latest_tool_result_of_the_request(self):
279
+ # The capture path: the most recent tool_result text in the
280
+ # conversation becomes the monitor snippet, so the NEXT turn's
281
+ # directive quotes what the repeated call actually returned.
282
+ mon = self.proxy.SessionMonitor(context_window=100000)
283
+ msgs = [{"role": "user", "content": "find the slug"}]
284
+ for i in range(2):
285
+ msgs.append({"role": "assistant", "content": [
286
+ {"type": "tool_use", "id": f"t{i}", "name": "Bash",
287
+ "input": {"command": "uap memory query x"}}]})
288
+ msgs.append({"role": "user", "content": [
289
+ {"type": "tool_result", "tool_use_id": f"t{i}",
290
+ "content": self.NO_RESULTS}]})
291
+ self.proxy._record_last_assistant_tool_calls({"messages": msgs}, mon)
292
+ self.assertIn("No results in short-term memory", mon.last_tool_result_snippet)