@miller-tech/uap 2.36.4 → 2.39.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/config/model-profiles/qwen38.json +3 -3
- package/config/model-registry.json +96 -6
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +57 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/deliver.d.ts +46 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +155 -36
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/models.d.ts +11 -1
- package/dist/cli/models.d.ts.map +1 -1
- package/dist/cli/models.js +164 -3
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/workflow.d.ts +36 -0
- package/dist/cli/workflow.d.ts.map +1 -0
- package/dist/cli/workflow.js +246 -0
- package/dist/cli/workflow.js.map +1 -0
- package/dist/delivery/run-state.d.ts +1 -1
- package/dist/delivery/run-state.d.ts.map +1 -1
- package/dist/delivery/run-state.js +1 -1
- package/dist/delivery/run-state.js.map +1 -1
- package/dist/delivery/team-solve-deliver.d.ts +83 -0
- package/dist/delivery/team-solve-deliver.d.ts.map +1 -0
- package/dist/delivery/team-solve-deliver.js +191 -0
- package/dist/delivery/team-solve-deliver.js.map +1 -0
- package/dist/inference/model-files.d.ts +111 -0
- package/dist/inference/model-files.d.ts.map +1 -0
- package/dist/inference/model-files.js +172 -0
- package/dist/inference/model-files.js.map +1 -0
- package/dist/placement/registry.d.ts +650 -0
- package/dist/placement/registry.d.ts.map +1 -1
- package/dist/placement/registry.js +21 -0
- package/dist/placement/registry.js.map +1 -1
- package/dist/workflow/loader.d.ts +34 -0
- package/dist/workflow/loader.d.ts.map +1 -0
- package/dist/workflow/loader.js +232 -0
- package/dist/workflow/loader.js.map +1 -0
- package/dist/workflow/runner.d.ts +90 -0
- package/dist/workflow/runner.d.ts.map +1 -0
- package/dist/workflow/runner.js +416 -0
- package/dist/workflow/runner.js.map +1 -0
- package/dist/workflow/types.d.ts +400 -0
- package/dist/workflow/types.d.ts.map +1 -0
- package/dist/workflow/types.js +147 -0
- package/dist/workflow/types.js.map +1 -0
- package/docs/INDEX.md +1 -0
- package/docs/architecture/adr/0013-declarative-workflow-layer.md +99 -0
- package/docs/architecture/adr/0014-loop-steps-and-team-solve-deliver.md +142 -0
- package/docs/benchmarks/LCB_DROID_SMOKE.md +111 -0
- package/docs/guides/DELIVER.md +3 -1
- package/docs/guides/WORKFLOWS.md +179 -0
- package/docs/plans/model-performance-proof-options.md +39 -0
- package/docs/reference/CLI.md +33 -0
- package/docs/reference/CONFIGURATION.md +1 -0
- package/docs/reference/FEATURES.md +18 -0
- package/examples/auto-coordinator-example.js +127 -0
- package/examples/demo-order-solid.ts +19 -0
- package/examples/order-processing-strategy-pattern.ts +186 -0
- package/examples/order-processor-refactored.ts +473 -0
- package/examples/order-solid-refactor.ts +830 -0
- package/examples/refactored-order-processing.ts +106 -0
- package/examples/run-order-processing.ts +48 -0
- package/examples/solid-order-processing.ts +324 -0
- package/examples/strategy-pattern-example.ts +100 -0
- package/examples/workflows/team-solve/README.md +85 -0
- package/examples/workflows/team-solve/check-samples.sh +50 -0
- package/examples/workflows/team-solve/sample/task.md +22 -0
- package/examples/workflows/team-solve/sample/tests/1.in +2 -0
- package/examples/workflows/team-solve/sample/tests/1.out +1 -0
- package/examples/workflows/team-solve/sample/tests/2.in +2 -0
- package/examples/workflows/team-solve/sample/tests/2.out +1 -0
- package/examples/workflows/team-solve/sample/tests/3.in +2 -0
- package/examples/workflows/team-solve/sample/tests/3.out +1 -0
- package/examples/workflows/team-solve/sample/tests/4.in +2 -0
- package/examples/workflows/team-solve/sample/tests/4.out +1 -0
- package/examples/workflows/team-solve/sample/tests/5.in +1 -0
- package/examples/workflows/team-solve/sample/tests/5.out +1 -0
- package/examples/workflows/team-solve/steps/ideate-a.md +26 -0
- package/examples/workflows/team-solve/steps/ideate-b.md +28 -0
- package/examples/workflows/team-solve/steps/plan.md +20 -0
- package/examples/workflows/team-solve/steps/repair.md +25 -0
- package/examples/workflows/team-solve/steps/review.md +29 -0
- package/examples/workflows/team-solve/steps/solve.md +26 -0
- package/examples/workflows/team-solve/workflow.yaml +78 -0
- package/examples/workflows/team-solve-deliver/README.md +84 -0
- package/examples/workflows/team-solve-deliver/steps/ideate-a.md +25 -0
- package/examples/workflows/team-solve-deliver/steps/ideate-b.md +28 -0
- package/examples/workflows/team-solve-deliver/steps/plan.md +25 -0
- package/examples/workflows/team-solve-deliver/steps/repair.md +20 -0
- package/examples/workflows/team-solve-deliver/steps/review.md +31 -0
- package/examples/workflows/team-solve-deliver/steps/solve.md +28 -0
- package/examples/workflows/team-solve-deliver/steps/verify.md +9 -0
- package/examples/workflows/team-solve-deliver/workflow.yaml +74 -0
- package/package.json +2 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-311.pyc +0 -0
- package/src/policies/enforcers/__pycache__/expert_review_required.cpython-311.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-311.pyc +0 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-311.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-311.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +163 -18
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# team-solve-deliver — the team directs the deliver convergence loop.
|
|
2
|
+
#
|
|
3
|
+
# The combination of the two engines: team-solve's role structure and ledger
|
|
4
|
+
# discipline (fresh contexts, files as the only channel, cross-correction)
|
|
5
|
+
# wrapped around deliver's convergence machinery (tools + the project's real
|
|
6
|
+
# completion gates). Roles think; loops build and verify.
|
|
7
|
+
#
|
|
8
|
+
# plan ──┬── ideate-a ──┬── solve ── review ── repair ── verify
|
|
9
|
+
# └── ideate-b ──┘ (loop) (judge) (loop) (loop, gates-only)
|
|
10
|
+
#
|
|
11
|
+
# solve/repair/verify are `kind: loop` steps — they execute through a
|
|
12
|
+
# CALLER-INJECTED convergence loop (uap deliver wires its own machinery and
|
|
13
|
+
# auto-engages this workflow for high/critical missions). A standalone
|
|
14
|
+
# `uap workflow run team-solve-deliver` fails closed: no loop provider.
|
|
15
|
+
name: team-solve-deliver
|
|
16
|
+
description: >
|
|
17
|
+
team-solve directing the deliver convergence loop for repo missions: an
|
|
18
|
+
approach plan, two parallel ideations from different engineering families,
|
|
19
|
+
a gates-driven solve loop, a fresh-context review, a review-seeded repair
|
|
20
|
+
loop, and a verify-only verdict. Auto-engaged by `uap deliver` on
|
|
21
|
+
high/critical complexity; force with `uap deliver --team-solve`.
|
|
22
|
+
parallel: 2
|
|
23
|
+
# 0 engine-level step retries: the solve/repair loops already converge
|
|
24
|
+
# internally (until-delivered + escalation inside the mission dispatch), and
|
|
25
|
+
# a workflow-level retry of `solve` would re-run the ENTIRE mission dispatch
|
|
26
|
+
# after it already burned its turn budget — doubling the hardest-tier spend
|
|
27
|
+
# and bypassing this workflow's own review→repair discipline.
|
|
28
|
+
maxRepairsPerStep: 0
|
|
29
|
+
steps:
|
|
30
|
+
- id: plan
|
|
31
|
+
kind: agent
|
|
32
|
+
title: Approach planner (judge rail)
|
|
33
|
+
prompt: steps/plan.md
|
|
34
|
+
writes: [plan.md]
|
|
35
|
+
requires: []
|
|
36
|
+
- id: ideate-a
|
|
37
|
+
kind: agent
|
|
38
|
+
title: Ideator A (minimal-change family)
|
|
39
|
+
prompt: steps/ideate-a.md
|
|
40
|
+
reads: [plan.md]
|
|
41
|
+
writes: [proposal-a.md]
|
|
42
|
+
requires: [plan]
|
|
43
|
+
- id: ideate-b
|
|
44
|
+
kind: agent
|
|
45
|
+
title: Ideator B (structural family)
|
|
46
|
+
prompt: steps/ideate-b.md
|
|
47
|
+
reads: [plan.md]
|
|
48
|
+
writes: [proposal-b.md]
|
|
49
|
+
requires: [plan]
|
|
50
|
+
- id: solve
|
|
51
|
+
kind: loop
|
|
52
|
+
title: Solve (convergence loop, project gates)
|
|
53
|
+
prompt: steps/solve.md
|
|
54
|
+
reads: [plan.md, proposal-a.md, proposal-b.md]
|
|
55
|
+
requires: [ideate-a, ideate-b]
|
|
56
|
+
- id: review
|
|
57
|
+
kind: agent
|
|
58
|
+
title: Fresh-context reviewer (judge rail)
|
|
59
|
+
prompt: steps/review.md
|
|
60
|
+
reads: [plan.md]
|
|
61
|
+
writes: [repair.md]
|
|
62
|
+
requires: [solve]
|
|
63
|
+
- id: repair
|
|
64
|
+
kind: loop
|
|
65
|
+
title: Repair (convergence loop, review-seeded)
|
|
66
|
+
prompt: steps/repair.md
|
|
67
|
+
reads: [repair.md]
|
|
68
|
+
requires: [review]
|
|
69
|
+
- id: verify
|
|
70
|
+
kind: loop
|
|
71
|
+
title: Verdict (gates only)
|
|
72
|
+
prompt: steps/verify.md
|
|
73
|
+
verifyOnly: true
|
|
74
|
+
requires: [repair]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miller-tech/uap",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.39.1",
|
|
4
4
|
"description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
"dist",
|
|
117
117
|
"web",
|
|
118
118
|
"docs",
|
|
119
|
+
"examples",
|
|
119
120
|
"config",
|
|
120
121
|
"templates",
|
|
121
122
|
"src/policies/schemas/policies",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1621,11 +1621,23 @@ PROXY_EMPTY_TOOL_LOOP_BREAK = int(
|
|
|
1621
1621
|
# ever-growing transcript (observed: ctx 936%, model emits tool_calls 18/min
|
|
1622
1622
|
# despite tool_choice=auto). After this many consecutive turns at/above the
|
|
1623
1623
|
# ratio, strip tools entirely so the only possible response is a terminal text
|
|
1624
|
-
# summary (end_turn), which ends the client's agentic loop.
|
|
1625
|
-
#
|
|
1626
|
-
#
|
|
1624
|
+
# summary (end_turn), which ends the client's agentic loop.
|
|
1625
|
+
#
|
|
1626
|
+
# Ratio sits above the merely-full band but inside the runaway band. A
|
|
1627
|
+
# merely-full session tops out near 100-141%: a client that honestly believes
|
|
1628
|
+
# a ~200k window (PROXY_CLIENT_ASSUMED_WINDOW) and compacts at 92.5% sends at
|
|
1629
|
+
# most ~185k against a 131,072 rail. A window-blind runaway plateaus wherever
|
|
1630
|
+
# its own belief caps it, and that plateau can sit far below 300%: live
|
|
1631
|
+
# 2026-11-06 (droid + flash-next, cognition-engine), a client with no
|
|
1632
|
+
# maxContextLimit (assumed 250k) ramped 132% -> 198% in 4 minutes and then sat
|
|
1633
|
+
# at 190-198% for 4+ HOURS — every turn CRITICAL-PRUNEd blind to ~6 messages
|
|
1634
|
+
# (~24-40k actually sent upstream), findings evaporating, the model re-reading
|
|
1635
|
+
# the same files — because the 3.0 gate was written for 936% runaways and this
|
|
1636
|
+
# one never crossed 200%. 1.6 clears the merely-full band (141% plus one
|
|
1637
|
+
# ~19k-token turn of headroom) while ending that class of treadmill within
|
|
1638
|
+
# STREAK turns. 0 disables.
|
|
1627
1639
|
PROXY_RAW_CTX_FINALIZE_RATIO = float(
|
|
1628
|
-
os.environ.get("PROXY_RAW_CTX_FINALIZE_RATIO", "
|
|
1640
|
+
os.environ.get("PROXY_RAW_CTX_FINALIZE_RATIO", "1.6")
|
|
1629
1641
|
)
|
|
1630
1642
|
PROXY_RAW_CTX_FINALIZE_STREAK = int(
|
|
1631
1643
|
os.environ.get("PROXY_RAW_CTX_FINALIZE_STREAK", "2")
|
|
@@ -2456,6 +2468,31 @@ PROXY_ATTRACTOR_TEMP_OVERRIDE = float(
|
|
|
2456
2468
|
PROXY_ATTRACTOR_FINALIZE_THRESHOLD = max(1, int(
|
|
2457
2469
|
os.environ.get("PROXY_ATTRACTOR_FINALIZE_THRESHOLD", "2")
|
|
2458
2470
|
))
|
|
2471
|
+
# Post-contamination low-temp clamp exemption. The standard contamination
|
|
2472
|
+
# reset clamps the next tool turn to temperature 0.10 (Cycle 15 Option 2:
|
|
2473
|
+
# deterministic output repairs malformed JSON). For DEGENERATE-CONTENT faults
|
|
2474
|
+
# (garbled args) determinism is exactly wrong: temp ~0 re-enters the same
|
|
2475
|
+
# degenerate attractor that triggered the reset (live 2026-10-30: 9
|
|
2476
|
+
# contamination-forced finalize stops in one day, the 0.10 clamp active on
|
|
2477
|
+
# every post-reset turn). Reset causes listed here keep the normal
|
|
2478
|
+
# PROXY_TOOL_TURN_TEMPERATURE instead of the 0.10 clamp. Empty string
|
|
2479
|
+
# disables the exemption entirely (always clamp).
|
|
2480
|
+
PROXY_CONTAMINATION_LOW_TEMP_EXEMPT_KINDS = {
|
|
2481
|
+
k.strip()
|
|
2482
|
+
for k in os.environ.get(
|
|
2483
|
+
"PROXY_CONTAMINATION_LOW_TEMP_EXEMPT_KINDS", "invalid_tool_args"
|
|
2484
|
+
).split(",")
|
|
2485
|
+
if k.strip()
|
|
2486
|
+
}
|
|
2487
|
+
# Late garbled-retry output cap. The deployed env pins
|
|
2488
|
+
# PROXY_TOOL_TURN_MAX_TOKENS_GARBLED high (32768, raised 2026-04-22 so legit
|
|
2489
|
+
# multi-file heredoc edits don't truncate), which also gives a degenerate
|
|
2490
|
+
# generation maximum room to run. From this retry attempt onward the budget
|
|
2491
|
+
# drops to this cap: the observed legit commands (~4.5K tokens) still fit,
|
|
2492
|
+
# while runaway degeneration is starved of room (live 2026-10-30: every
|
|
2493
|
+
# garbled call was a long inline heredoc; short commands never garbled).
|
|
2494
|
+
# 0 disables the late cap.
|
|
2495
|
+
PROXY_GARBLE_LATE_CAP = int(os.environ.get("PROXY_GARBLE_LATE_CAP", "8192"))
|
|
2459
2496
|
PROXY_AGENTIC_SUPPLEMENT_MODE = (
|
|
2460
2497
|
os.environ.get("PROXY_AGENTIC_SUPPLEMENT_MODE", "clean").strip().lower()
|
|
2461
2498
|
)
|
|
@@ -3119,6 +3156,7 @@ class SessionMonitor:
|
|
|
3119
3156
|
invalid_tool_call_streak: int = 0 # consecutive invalid tool arg payloads
|
|
3120
3157
|
required_tool_miss_streak: int = 0 # required tool turns with no tool call
|
|
3121
3158
|
contamination_resets: int = 0 # how many contamination resets were applied
|
|
3159
|
+
contamination_cause: str = "" # dominant streak behind the last reset ("invalid_tool_args"/"malformed_payload"/"required_tool_miss"/"generic")
|
|
3122
3160
|
last_fault_excerpt_hash: str = "" # hash of last TOOL RESPONSE ISSUE excerpt (attractor detection)
|
|
3123
3161
|
attractor_correction_active: bool = False # next turn uses high-temp escape sampling
|
|
3124
3162
|
forced_auto_cooldown_turns: int = 0 # temporary auto override turns remaining
|
|
@@ -9822,13 +9860,24 @@ def build_openai_request(
|
|
|
9822
9860
|
client_temp,
|
|
9823
9861
|
)
|
|
9824
9862
|
else:
|
|
9825
|
-
|
|
9863
|
+
# Cycle 15 Option 2: lower temperature after contamination resets —
|
|
9864
|
+
# EXCEPT when the reset was driven by an exempt cause: for
|
|
9865
|
+
# degenerate-content faults (garbled args) determinism re-enters
|
|
9866
|
+
# the same attractor that triggered the reset (live 2026-10-30).
|
|
9867
|
+
if (
|
|
9868
|
+
monitor.contamination_resets > 0
|
|
9869
|
+
and monitor.contamination_cause
|
|
9870
|
+
not in PROXY_CONTAMINATION_LOW_TEMP_EXEMPT_KINDS
|
|
9871
|
+
):
|
|
9826
9872
|
target_temp = min(target_temp, 0.1)
|
|
9827
9873
|
if client_temp is None or client_temp > target_temp:
|
|
9828
9874
|
openai_body["temperature"] = target_temp
|
|
9829
9875
|
extra = ""
|
|
9830
9876
|
if monitor.contamination_resets > 0:
|
|
9831
|
-
extra =
|
|
9877
|
+
extra = (
|
|
9878
|
+
f" (post-contamination reset, resets={monitor.contamination_resets},"
|
|
9879
|
+
f" cause={monitor.contamination_cause or 'generic'})"
|
|
9880
|
+
)
|
|
9832
9881
|
logger.info(
|
|
9833
9882
|
"TOOL TURN TEMP: forcing temperature=%.2f (was %s) for tool-enabled request%s",
|
|
9834
9883
|
target_temp,
|
|
@@ -10717,6 +10766,68 @@ def _hash_fault_excerpt(excerpt: str) -> str:
|
|
|
10717
10766
|
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()[:16]
|
|
10718
10767
|
|
|
10719
10768
|
|
|
10769
|
+
def _fault_fingerprint(
|
|
10770
|
+
excerpt: str, arg_excerpt: str, fault_tool_names: list[str]
|
|
10771
|
+
) -> str:
|
|
10772
|
+
"""Stable fingerprint of a tool-response fault for attractor-repeat detection.
|
|
10773
|
+
|
|
10774
|
+
Tool-call faults carry NO assistant text (every observed garble incident
|
|
10775
|
+
logged text_len=0), so hashing only the text excerpt yielded "" — falsy —
|
|
10776
|
+
and the attractor detector (hard reset + temp escape + lowered finalize
|
|
10777
|
+
threshold) never fired for the dominant fault class (live 2026-10-30:
|
|
10778
|
+
9 contamination-forced finalize stops, none attractor-detected). Fall
|
|
10779
|
+
back to the garbled-args excerpt — its stable command head survives
|
|
10780
|
+
normalization, so the same degenerate approach hashes equal across
|
|
10781
|
+
resets — then the failing tool names.
|
|
10782
|
+
"""
|
|
10783
|
+
if excerpt:
|
|
10784
|
+
return _hash_fault_excerpt(excerpt)
|
|
10785
|
+
if arg_excerpt:
|
|
10786
|
+
return _hash_fault_excerpt(arg_excerpt)
|
|
10787
|
+
if fault_tool_names:
|
|
10788
|
+
return _hash_fault_excerpt(
|
|
10789
|
+
"tool:" + ",".join(sorted(set(fault_tool_names)))
|
|
10790
|
+
)
|
|
10791
|
+
return ""
|
|
10792
|
+
|
|
10793
|
+
|
|
10794
|
+
def _retry_temperature(attempt: int, is_garbled: bool) -> float:
|
|
10795
|
+
"""Retry temperature ladder for malformed/garbled tool-call retries.
|
|
10796
|
+
|
|
10797
|
+
Attempt 1: configured retry temp (deterministic first try).
|
|
10798
|
+
Attempt 2: 0.5 — escape the degenerate local minimum (Cycle 15 Option 3).
|
|
10799
|
+
Attempt 3+ garbled (degenerate CONTENT): attractor-escape temperature —
|
|
10800
|
+
determinism re-enters the same degenerate trajectory (live 2026-10-30:
|
|
10801
|
+
garbled attempts 1-4 all reproduced at 0.05/0.5 while short commands
|
|
10802
|
+
never garbled); malformed-JSON retries keep the deterministic ladder
|
|
10803
|
+
because structure repair wants determinism.
|
|
10804
|
+
"""
|
|
10805
|
+
if is_garbled and attempt >= 3:
|
|
10806
|
+
return max(
|
|
10807
|
+
PROXY_ATTRACTOR_TEMP_OVERRIDE, PROXY_MALFORMED_TOOL_RETRY_TEMPERATURE
|
|
10808
|
+
)
|
|
10809
|
+
if attempt > 1:
|
|
10810
|
+
return 0.5
|
|
10811
|
+
return PROXY_MALFORMED_TOOL_RETRY_TEMPERATURE
|
|
10812
|
+
|
|
10813
|
+
|
|
10814
|
+
def _contamination_reset_cause(monitor: "SessionMonitor") -> str:
|
|
10815
|
+
"""Dominant output-quality streak behind a contamination reset.
|
|
10816
|
+
|
|
10817
|
+
Drives the post-reset temperature policy: degenerate-content faults
|
|
10818
|
+
(invalid_tool_args) are EXEMPT from the 0.10 low-temp clamp (see
|
|
10819
|
+
PROXY_CONTAMINATION_LOW_TEMP_EXEMPT_KINDS) because determinism re-enters
|
|
10820
|
+
the same attractor; malformed-JSON repairs keep the clamp.
|
|
10821
|
+
"""
|
|
10822
|
+
if monitor.invalid_tool_call_streak > monitor.malformed_tool_streak:
|
|
10823
|
+
return "invalid_tool_args"
|
|
10824
|
+
if monitor.malformed_tool_streak > 0:
|
|
10825
|
+
return "malformed_payload"
|
|
10826
|
+
if monitor.required_tool_miss_streak > 0:
|
|
10827
|
+
return "required_tool_miss"
|
|
10828
|
+
return "generic"
|
|
10829
|
+
|
|
10830
|
+
|
|
10720
10831
|
def _extract_openai_tool_calls(openai_resp: dict) -> list[dict]:
|
|
10721
10832
|
_, message = _extract_openai_choice(openai_resp)
|
|
10722
10833
|
tool_calls = message.get("tool_calls") or []
|
|
@@ -12399,10 +12510,20 @@ def _validate_tool_call_arguments(
|
|
|
12399
12510
|
allowed_tools: set[str],
|
|
12400
12511
|
) -> ToolResponseIssue:
|
|
12401
12512
|
if allowed_tools and tool_name not in allowed_tools:
|
|
12513
|
+
# 2026-10-30: name the kept alternatives — a narrowed retry (the
|
|
12514
|
+
# garbling tool excluded) observed the model emitting the excluded
|
|
12515
|
+
# tool anyway because "use the provided tool list" gave it nothing
|
|
12516
|
+
# concrete to switch to.
|
|
12517
|
+
known = ", ".join(sorted(allowed_tools)[:6])
|
|
12402
12518
|
return ToolResponseIssue(
|
|
12403
12519
|
kind="invalid_tool_args",
|
|
12404
12520
|
reason=f"unknown tool '{tool_name}'",
|
|
12405
|
-
retry_hint=
|
|
12521
|
+
retry_hint=(
|
|
12522
|
+
f"'{tool_name}' is not available on this turn. "
|
|
12523
|
+
f"Call exactly one of: {known}."
|
|
12524
|
+
if known
|
|
12525
|
+
else "Use exactly one tool from the provided tool list."
|
|
12526
|
+
),
|
|
12406
12527
|
)
|
|
12407
12528
|
|
|
12408
12529
|
if isinstance(raw_arguments, (dict, list)):
|
|
@@ -12474,7 +12595,10 @@ def _validate_tool_call_arguments(
|
|
|
12474
12595
|
reason=f"arguments for '{tool_name}' contain garbled/degenerate content",
|
|
12475
12596
|
retry_hint=(
|
|
12476
12597
|
f"Emit exactly one `{tool_name}` tool call with well-formed JSON arguments. "
|
|
12477
|
-
"
|
|
12598
|
+
"Your previous arguments degenerated (repeated digit or brace runs). "
|
|
12599
|
+
"SIMPLIFY and SHORTEN the operation: split the work into smaller steps, "
|
|
12600
|
+
"avoid long inline heredocs and large generated numeric blocks, and "
|
|
12601
|
+
"prefer short commands. Do not repeat closing braces, brackets, or digits."
|
|
12478
12602
|
),
|
|
12479
12603
|
)
|
|
12480
12604
|
|
|
@@ -13053,12 +13177,16 @@ def _build_malformed_retry_body(
|
|
|
13053
13177
|
retry_body["stream"] = False
|
|
13054
13178
|
retry_body["tool_choice"] = tool_choice
|
|
13055
13179
|
# Cycle 15 Option 3: vary temperature across retries to break degenerate patterns.
|
|
13056
|
-
#
|
|
13057
|
-
#
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13180
|
+
# Ladder lives in _retry_temperature (garbled retries escalate to the
|
|
13181
|
+
# attractor-escape temperature from attempt 3 — see its docstring).
|
|
13182
|
+
retry_body["temperature"] = _retry_temperature(attempt, is_garbled)
|
|
13183
|
+
if is_garbled and attempt >= 3 and PROXY_GARBLE_LATE_CAP > 0:
|
|
13184
|
+
logger.info(
|
|
13185
|
+
"RETRY GARBLED ESCALATION: temperature=%.2f late_cap=%d for garbled retry attempt=%d",
|
|
13186
|
+
retry_body["temperature"],
|
|
13187
|
+
PROXY_GARBLE_LATE_CAP,
|
|
13188
|
+
attempt,
|
|
13189
|
+
)
|
|
13062
13190
|
|
|
13063
13191
|
if tool_choice == "required":
|
|
13064
13192
|
retry_instruction = (
|
|
@@ -13090,7 +13218,17 @@ def _build_malformed_retry_body(
|
|
|
13090
13218
|
|
|
13091
13219
|
# Option 1: Progressive garbled-cap within retries — use smaller max_tokens
|
|
13092
13220
|
# when the issue involves garbled/degenerate args to limit degeneration room.
|
|
13093
|
-
|
|
13221
|
+
# 2026-10-30: the deployed env pins PROXY_TOOL_TURN_MAX_TOKENS_GARBLED high
|
|
13222
|
+
# (32768) so legit multi-file heredoc edits don't truncate (2026-04-22) —
|
|
13223
|
+
# from attempt 3 onward, starve degeneration room with the late cap.
|
|
13224
|
+
if is_garbled and attempt >= 3 and PROXY_GARBLE_LATE_CAP > 0:
|
|
13225
|
+
retry_body["max_tokens"] = PROXY_GARBLE_LATE_CAP
|
|
13226
|
+
logger.info(
|
|
13227
|
+
"RETRY GARBLED LATE CAP: max_tokens=%d for garbled retry attempt=%d",
|
|
13228
|
+
PROXY_GARBLE_LATE_CAP,
|
|
13229
|
+
attempt,
|
|
13230
|
+
)
|
|
13231
|
+
elif is_garbled and PROXY_TOOL_TURN_MAX_TOKENS_GARBLED > 0:
|
|
13094
13232
|
retry_body["max_tokens"] = PROXY_TOOL_TURN_MAX_TOKENS_GARBLED
|
|
13095
13233
|
logger.info(
|
|
13096
13234
|
"RETRY GARBLED CAP: max_tokens=%d for garbled retry attempt=%d",
|
|
@@ -13435,17 +13573,22 @@ async def _apply_malformed_tool_guardrail(
|
|
|
13435
13573
|
excerpt = _openai_message_text(working_resp)[:220].replace("\n", " ")
|
|
13436
13574
|
# Option 2: Log garbled argument content for diagnostics
|
|
13437
13575
|
arg_excerpt = ""
|
|
13576
|
+
fault_tool_names: list[str] = []
|
|
13438
13577
|
if issue.kind == "invalid_tool_args":
|
|
13439
13578
|
for tc in (working_resp.get("choices", [{}])[0].get("message", {}).get("tool_calls") or []):
|
|
13579
|
+
fn_name = tc.get("function", {}).get("name", "")
|
|
13440
13580
|
raw_args = tc.get("function", {}).get("arguments", "")
|
|
13441
|
-
if
|
|
13581
|
+
if fn_name:
|
|
13582
|
+
fault_tool_names.append(fn_name)
|
|
13583
|
+
if raw_args and _is_garbled_tool_arguments(raw_args) and not arg_excerpt:
|
|
13442
13584
|
arg_excerpt = raw_args[:200].replace("\n", " ")
|
|
13443
|
-
break
|
|
13444
13585
|
# Attractor detection — hash the normalized fault excerpt so the
|
|
13445
13586
|
# contamination breaker can recognize the same fixed-point response
|
|
13446
13587
|
# reappearing across consecutive resets. Whitespace-normalized so trivial
|
|
13447
13588
|
# rendering differences don't break the match.
|
|
13448
|
-
monitor.last_fault_excerpt_hash =
|
|
13589
|
+
monitor.last_fault_excerpt_hash = _fault_fingerprint(
|
|
13590
|
+
excerpt, arg_excerpt, fault_tool_names
|
|
13591
|
+
)
|
|
13449
13592
|
logger.warning(
|
|
13450
13593
|
"TOOL RESPONSE ISSUE: session=%s kind=%s reason=%s malformed=%d invalid=%d required_miss=%d excerpt=%.220s args=%.200s",
|
|
13451
13594
|
session_id,
|
|
@@ -13801,6 +13944,7 @@ def _maybe_apply_session_contamination_breaker(
|
|
|
13801
13944
|
max_contamination_resets,
|
|
13802
13945
|
)
|
|
13803
13946
|
monitor.set_tool_turn_phase("finalize", reason="contamination_loop")
|
|
13947
|
+
monitor.contamination_cause = _contamination_reset_cause(monitor)
|
|
13804
13948
|
monitor.contamination_resets += 1
|
|
13805
13949
|
monitor.malformed_tool_streak = 0
|
|
13806
13950
|
monitor.invalid_tool_call_streak = 0
|
|
@@ -13923,6 +14067,7 @@ def _maybe_apply_session_contamination_breaker(
|
|
|
13923
14067
|
|
|
13924
14068
|
forced_before = monitor.consecutive_forced_count
|
|
13925
14069
|
required_miss_before = monitor.required_tool_miss_streak
|
|
14070
|
+
monitor.contamination_cause = _contamination_reset_cause(monitor)
|
|
13926
14071
|
monitor.contamination_resets += 1
|
|
13927
14072
|
# Emergency tier: the conversation itself is rewritten, so BOTH the
|
|
13928
14073
|
# forcing-loop family (via its one policy point) and the output-quality
|