@miller-tech/uap 1.220.4 → 1.220.5
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/package.json +2 -2
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -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 +31 -0
- package/tools/agents/tests/test_stuck_break_hard.py +87 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miller-tech/uap",
|
|
3
|
-
"version": "1.220.
|
|
3
|
+
"version": "1.220.5",
|
|
4
4
|
"description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"start": "node dist/bin/cli.js",
|
|
22
22
|
"test": "vitest",
|
|
23
23
|
"test:ci": "vitest run",
|
|
24
|
-
"test:enforcers": "UAP_PROXY_ENV_AUTOLOAD=0 python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_conditional_destructive_verbs tools.agents.tests.test_live_deliver_kill tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_schema_diff_inline tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_gate_evidence tools.agents.tests.test_repeat_call_guard tools.agents.tests.test_gate_integrity tools.agents.tests.test_gate_failclosed_indirection tools.agents.tests.test_gate_failclosed_schema_diff tools.agents.tests.test_rtk_wrap tools.agents.tests.test_expert_review_pr_scope tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_write_complexity tools.agents.tests.test_fastpath_config tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate tools.agents.tests.test_validate_plan_inside_project tools.agents.tests.test_anthropic_proxy_streaming tools.agents.tests.test_non_stream_resp_log tools.agents.tests.test_delivery_enforcement_worktree tools.agents.tests.test_output_token_ceilings tools.agents.tests.test_tool_narrowing_core tools.agents.tests.test_models_context_window tools.agents.tests.test_thinking_template_kwargs tools.agents.tests.test_cycle_break_wait_poll",
|
|
24
|
+
"test:enforcers": "UAP_PROXY_ENV_AUTOLOAD=0 python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_conditional_destructive_verbs tools.agents.tests.test_live_deliver_kill tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_schema_diff_inline tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_gate_evidence tools.agents.tests.test_repeat_call_guard tools.agents.tests.test_gate_integrity tools.agents.tests.test_gate_failclosed_indirection tools.agents.tests.test_gate_failclosed_schema_diff tools.agents.tests.test_rtk_wrap tools.agents.tests.test_expert_review_pr_scope tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_write_complexity tools.agents.tests.test_fastpath_config tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_stuck_break_hard tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate tools.agents.tests.test_validate_plan_inside_project tools.agents.tests.test_anthropic_proxy_streaming tools.agents.tests.test_non_stream_resp_log tools.agents.tests.test_delivery_enforcement_worktree tools.agents.tests.test_output_token_ceilings tools.agents.tests.test_tool_narrowing_core tools.agents.tests.test_models_context_window tools.agents.tests.test_thinking_template_kwargs tools.agents.tests.test_cycle_break_wait_poll",
|
|
25
25
|
"test:coverage": "vitest --coverage",
|
|
26
26
|
"bench": "vitest --config vitest.bench.config.ts",
|
|
27
27
|
"lint": "eslint src --ext .ts",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -364,6 +364,12 @@ PROXY_CONTEXT_RELEASE_THRESHOLD = float(
|
|
|
364
364
|
PROXY_STUCK_BREAK = os.environ.get("PROXY_STUCK_BREAK", "on").lower() not in {
|
|
365
365
|
"0", "false", "off", "no",
|
|
366
366
|
}
|
|
367
|
+
# After this many STUCK-BREAK fires in a session the break becomes HARD: the
|
|
368
|
+
# tools are removed from that turn so the only possible reply is prose, which
|
|
369
|
+
# ends the client's agent loop. The advisory break alone (directive + released
|
|
370
|
+
# tool_choice) is ignored by a weak local model: observed 2026-08-23, the same
|
|
371
|
+
# bash call repeated 13+ turns with fires=33 and not one prose reply. 0 = never.
|
|
372
|
+
PROXY_STUCK_BREAK_HARD_FIRES = int(os.environ.get("PROXY_STUCK_BREAK_HARD_FIRES", "3"))
|
|
367
373
|
# Self-reported-stuck phrases (lowercased match). Deliberately narrow.
|
|
368
374
|
_STUCK_PHRASE_RE = re.compile(
|
|
369
375
|
r"stuck in a loop|been stuck|break out of (?:this|the) loop|going in circles|"
|
|
@@ -6408,6 +6414,31 @@ def _maybe_inject_stuck_break(openai_body: dict, monitor: "SessionMonitor") -> N
|
|
|
6408
6414
|
else:
|
|
6409
6415
|
msgs.insert(0, {"role": "system", "content": directive.strip()})
|
|
6410
6416
|
openai_body["messages"] = msgs # reattach in case messages was empty/absent
|
|
6417
|
+
# HARD tier: the directive has been ignored PROXY_STUCK_BREAK_HARD_FIRES
|
|
6418
|
+
# times -- stop offering the tool at all. With no tools the model can only
|
|
6419
|
+
# answer in prose; the response-side XML resurrection is suppressed for
|
|
6420
|
+
# this turn so a "<tool_call>" written into the text cannot re-arm the loop
|
|
6421
|
+
# (same mechanism the finalize turn uses). Per-turn: the flag is cleared at
|
|
6422
|
+
# the next request entry and the tools come back with the next user turn.
|
|
6423
|
+
hard = PROXY_STUCK_BREAK_HARD_FIRES > 0 and monitor.stuck_break_fires >= PROXY_STUCK_BREAK_HARD_FIRES
|
|
6424
|
+
if hard:
|
|
6425
|
+
stripped = len(openai_body.get("tools") or [])
|
|
6426
|
+
openai_body.pop("tools", None)
|
|
6427
|
+
openai_body.pop("tool_choice", None)
|
|
6428
|
+
openai_body.pop("grammar", None)
|
|
6429
|
+
monitor.suppress_text_tool_extraction = True
|
|
6430
|
+
if msgs and msgs[0].get("role") == "system":
|
|
6431
|
+
msgs[0]["content"] = (msgs[0].get("content") or "") + (
|
|
6432
|
+
"\n\nTools are NOT available this turn. Reply in plain text only: "
|
|
6433
|
+
"what is done, what the repeated call returned, and the single next "
|
|
6434
|
+
"step or blocking question."
|
|
6435
|
+
)
|
|
6436
|
+
logger.warning(
|
|
6437
|
+
"STUCK-BREAK HARD: tools stripped for this turn (%s, fires=%d, stripped=%d) -- "
|
|
6438
|
+
"the advisory break was ignored; forcing a prose exit",
|
|
6439
|
+
reason, monitor.stuck_break_fires, stripped,
|
|
6440
|
+
)
|
|
6441
|
+
return
|
|
6411
6442
|
logger.warning("STUCK-BREAK: forced terminal turn (%s, fires=%d)", reason, monitor.stuck_break_fires)
|
|
6412
6443
|
|
|
6413
6444
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""STUCK-BREAK hard tier.
|
|
3
|
+
|
|
4
|
+
The advisory STUCK-BREAK (directive + tool_choice released to 'auto') leaves
|
|
5
|
+
every tool on the table, and a weak local model just issues the same call
|
|
6
|
+
again: observed live 2026-08-23 16:46-17:45, the identical bash call 13+ turns
|
|
7
|
+
in a row with fires=33 and never a prose reply. After
|
|
8
|
+
PROXY_STUCK_BREAK_HARD_FIRES fires the break must be HARD -- tools removed for
|
|
9
|
+
that turn, XML tool-call resurrection suppressed -- so the turn can only end in
|
|
10
|
+
prose and the client's agent loop actually terminates.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import importlib.util
|
|
14
|
+
import unittest
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _load_proxy_module():
|
|
19
|
+
proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
|
|
20
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
21
|
+
assert spec is not None and spec.loader is not None
|
|
22
|
+
module = importlib.util.module_from_spec(spec)
|
|
23
|
+
spec.loader.exec_module(module)
|
|
24
|
+
return module
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
proxy = _load_proxy_module()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _looping_monitor(fires_so_far: int):
|
|
31
|
+
mon = proxy.SessionMonitor()
|
|
32
|
+
mon.self_stuck_streak = proxy.PROXY_STUCK_TEXT_THRESHOLD + 1
|
|
33
|
+
mon.stuck_break_fires = fires_so_far
|
|
34
|
+
return mon
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _body():
|
|
38
|
+
return {
|
|
39
|
+
"tool_choice": "required",
|
|
40
|
+
"tools": [{"type": "function", "function": {"name": "bash", "parameters": {}}}],
|
|
41
|
+
"messages": [{"role": "system", "content": "sys"}, {"role": "user", "content": "go"}],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestStuckBreakHard(unittest.TestCase):
|
|
46
|
+
def test_below_hard_threshold_stays_advisory(self):
|
|
47
|
+
mon = _looping_monitor(fires_so_far=0)
|
|
48
|
+
body = _body()
|
|
49
|
+
proxy._maybe_inject_stuck_break(body, mon)
|
|
50
|
+
self.assertEqual(mon.stuck_break_fires, 1)
|
|
51
|
+
self.assertIn("tools", body, "advisory tier keeps the tools")
|
|
52
|
+
self.assertEqual(body["tool_choice"], "auto")
|
|
53
|
+
self.assertFalse(mon.suppress_text_tool_extraction)
|
|
54
|
+
self.assertIn("STOP", body["messages"][0]["content"])
|
|
55
|
+
|
|
56
|
+
def test_hard_threshold_strips_tools_and_suppresses_xml_resurrection(self):
|
|
57
|
+
mon = _looping_monitor(fires_so_far=proxy.PROXY_STUCK_BREAK_HARD_FIRES - 1)
|
|
58
|
+
body = _body()
|
|
59
|
+
proxy._maybe_inject_stuck_break(body, mon)
|
|
60
|
+
self.assertEqual(mon.stuck_break_fires, proxy.PROXY_STUCK_BREAK_HARD_FIRES)
|
|
61
|
+
self.assertNotIn("tools", body)
|
|
62
|
+
self.assertNotIn("tool_choice", body)
|
|
63
|
+
self.assertTrue(mon.suppress_text_tool_extraction)
|
|
64
|
+
self.assertIn("plain text", body["messages"][0]["content"])
|
|
65
|
+
self.assertIn("STOP", body["messages"][0]["content"]) # the advisory text still travels
|
|
66
|
+
|
|
67
|
+
def test_hard_tier_can_be_disabled(self):
|
|
68
|
+
saved = proxy.PROXY_STUCK_BREAK_HARD_FIRES
|
|
69
|
+
try:
|
|
70
|
+
proxy.PROXY_STUCK_BREAK_HARD_FIRES = 0
|
|
71
|
+
mon = _looping_monitor(fires_so_far=50)
|
|
72
|
+
body = _body()
|
|
73
|
+
proxy._maybe_inject_stuck_break(body, mon)
|
|
74
|
+
self.assertIn("tools", body)
|
|
75
|
+
self.assertFalse(mon.suppress_text_tool_extraction)
|
|
76
|
+
finally:
|
|
77
|
+
proxy.PROXY_STUCK_BREAK_HARD_FIRES = saved
|
|
78
|
+
|
|
79
|
+
def test_default_threshold_is_small(self):
|
|
80
|
+
# Three ignored breaks is already ~8 minutes of identical calls on the
|
|
81
|
+
# local executor; the default must not let a loop run for an hour.
|
|
82
|
+
self.assertGreater(proxy.PROXY_STUCK_BREAK_HARD_FIRES, 0)
|
|
83
|
+
self.assertLessEqual(proxy.PROXY_STUCK_BREAK_HARD_FIRES, 5)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
unittest.main()
|