@miller-tech/uap 2.36.4 → 2.39.0
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 +147 -14
|
@@ -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.0",
|
|
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
|
|
@@ -2456,6 +2456,31 @@ PROXY_ATTRACTOR_TEMP_OVERRIDE = float(
|
|
|
2456
2456
|
PROXY_ATTRACTOR_FINALIZE_THRESHOLD = max(1, int(
|
|
2457
2457
|
os.environ.get("PROXY_ATTRACTOR_FINALIZE_THRESHOLD", "2")
|
|
2458
2458
|
))
|
|
2459
|
+
# Post-contamination low-temp clamp exemption. The standard contamination
|
|
2460
|
+
# reset clamps the next tool turn to temperature 0.10 (Cycle 15 Option 2:
|
|
2461
|
+
# deterministic output repairs malformed JSON). For DEGENERATE-CONTENT faults
|
|
2462
|
+
# (garbled args) determinism is exactly wrong: temp ~0 re-enters the same
|
|
2463
|
+
# degenerate attractor that triggered the reset (live 2026-10-30: 9
|
|
2464
|
+
# contamination-forced finalize stops in one day, the 0.10 clamp active on
|
|
2465
|
+
# every post-reset turn). Reset causes listed here keep the normal
|
|
2466
|
+
# PROXY_TOOL_TURN_TEMPERATURE instead of the 0.10 clamp. Empty string
|
|
2467
|
+
# disables the exemption entirely (always clamp).
|
|
2468
|
+
PROXY_CONTAMINATION_LOW_TEMP_EXEMPT_KINDS = {
|
|
2469
|
+
k.strip()
|
|
2470
|
+
for k in os.environ.get(
|
|
2471
|
+
"PROXY_CONTAMINATION_LOW_TEMP_EXEMPT_KINDS", "invalid_tool_args"
|
|
2472
|
+
).split(",")
|
|
2473
|
+
if k.strip()
|
|
2474
|
+
}
|
|
2475
|
+
# Late garbled-retry output cap. The deployed env pins
|
|
2476
|
+
# PROXY_TOOL_TURN_MAX_TOKENS_GARBLED high (32768, raised 2026-04-22 so legit
|
|
2477
|
+
# multi-file heredoc edits don't truncate), which also gives a degenerate
|
|
2478
|
+
# generation maximum room to run. From this retry attempt onward the budget
|
|
2479
|
+
# drops to this cap: the observed legit commands (~4.5K tokens) still fit,
|
|
2480
|
+
# while runaway degeneration is starved of room (live 2026-10-30: every
|
|
2481
|
+
# garbled call was a long inline heredoc; short commands never garbled).
|
|
2482
|
+
# 0 disables the late cap.
|
|
2483
|
+
PROXY_GARBLE_LATE_CAP = int(os.environ.get("PROXY_GARBLE_LATE_CAP", "8192"))
|
|
2459
2484
|
PROXY_AGENTIC_SUPPLEMENT_MODE = (
|
|
2460
2485
|
os.environ.get("PROXY_AGENTIC_SUPPLEMENT_MODE", "clean").strip().lower()
|
|
2461
2486
|
)
|
|
@@ -3119,6 +3144,7 @@ class SessionMonitor:
|
|
|
3119
3144
|
invalid_tool_call_streak: int = 0 # consecutive invalid tool arg payloads
|
|
3120
3145
|
required_tool_miss_streak: int = 0 # required tool turns with no tool call
|
|
3121
3146
|
contamination_resets: int = 0 # how many contamination resets were applied
|
|
3147
|
+
contamination_cause: str = "" # dominant streak behind the last reset ("invalid_tool_args"/"malformed_payload"/"required_tool_miss"/"generic")
|
|
3122
3148
|
last_fault_excerpt_hash: str = "" # hash of last TOOL RESPONSE ISSUE excerpt (attractor detection)
|
|
3123
3149
|
attractor_correction_active: bool = False # next turn uses high-temp escape sampling
|
|
3124
3150
|
forced_auto_cooldown_turns: int = 0 # temporary auto override turns remaining
|
|
@@ -9822,13 +9848,24 @@ def build_openai_request(
|
|
|
9822
9848
|
client_temp,
|
|
9823
9849
|
)
|
|
9824
9850
|
else:
|
|
9825
|
-
|
|
9851
|
+
# Cycle 15 Option 2: lower temperature after contamination resets —
|
|
9852
|
+
# EXCEPT when the reset was driven by an exempt cause: for
|
|
9853
|
+
# degenerate-content faults (garbled args) determinism re-enters
|
|
9854
|
+
# the same attractor that triggered the reset (live 2026-10-30).
|
|
9855
|
+
if (
|
|
9856
|
+
monitor.contamination_resets > 0
|
|
9857
|
+
and monitor.contamination_cause
|
|
9858
|
+
not in PROXY_CONTAMINATION_LOW_TEMP_EXEMPT_KINDS
|
|
9859
|
+
):
|
|
9826
9860
|
target_temp = min(target_temp, 0.1)
|
|
9827
9861
|
if client_temp is None or client_temp > target_temp:
|
|
9828
9862
|
openai_body["temperature"] = target_temp
|
|
9829
9863
|
extra = ""
|
|
9830
9864
|
if monitor.contamination_resets > 0:
|
|
9831
|
-
extra =
|
|
9865
|
+
extra = (
|
|
9866
|
+
f" (post-contamination reset, resets={monitor.contamination_resets},"
|
|
9867
|
+
f" cause={monitor.contamination_cause or 'generic'})"
|
|
9868
|
+
)
|
|
9832
9869
|
logger.info(
|
|
9833
9870
|
"TOOL TURN TEMP: forcing temperature=%.2f (was %s) for tool-enabled request%s",
|
|
9834
9871
|
target_temp,
|
|
@@ -10717,6 +10754,68 @@ def _hash_fault_excerpt(excerpt: str) -> str:
|
|
|
10717
10754
|
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()[:16]
|
|
10718
10755
|
|
|
10719
10756
|
|
|
10757
|
+
def _fault_fingerprint(
|
|
10758
|
+
excerpt: str, arg_excerpt: str, fault_tool_names: list[str]
|
|
10759
|
+
) -> str:
|
|
10760
|
+
"""Stable fingerprint of a tool-response fault for attractor-repeat detection.
|
|
10761
|
+
|
|
10762
|
+
Tool-call faults carry NO assistant text (every observed garble incident
|
|
10763
|
+
logged text_len=0), so hashing only the text excerpt yielded "" — falsy —
|
|
10764
|
+
and the attractor detector (hard reset + temp escape + lowered finalize
|
|
10765
|
+
threshold) never fired for the dominant fault class (live 2026-10-30:
|
|
10766
|
+
9 contamination-forced finalize stops, none attractor-detected). Fall
|
|
10767
|
+
back to the garbled-args excerpt — its stable command head survives
|
|
10768
|
+
normalization, so the same degenerate approach hashes equal across
|
|
10769
|
+
resets — then the failing tool names.
|
|
10770
|
+
"""
|
|
10771
|
+
if excerpt:
|
|
10772
|
+
return _hash_fault_excerpt(excerpt)
|
|
10773
|
+
if arg_excerpt:
|
|
10774
|
+
return _hash_fault_excerpt(arg_excerpt)
|
|
10775
|
+
if fault_tool_names:
|
|
10776
|
+
return _hash_fault_excerpt(
|
|
10777
|
+
"tool:" + ",".join(sorted(set(fault_tool_names)))
|
|
10778
|
+
)
|
|
10779
|
+
return ""
|
|
10780
|
+
|
|
10781
|
+
|
|
10782
|
+
def _retry_temperature(attempt: int, is_garbled: bool) -> float:
|
|
10783
|
+
"""Retry temperature ladder for malformed/garbled tool-call retries.
|
|
10784
|
+
|
|
10785
|
+
Attempt 1: configured retry temp (deterministic first try).
|
|
10786
|
+
Attempt 2: 0.5 — escape the degenerate local minimum (Cycle 15 Option 3).
|
|
10787
|
+
Attempt 3+ garbled (degenerate CONTENT): attractor-escape temperature —
|
|
10788
|
+
determinism re-enters the same degenerate trajectory (live 2026-10-30:
|
|
10789
|
+
garbled attempts 1-4 all reproduced at 0.05/0.5 while short commands
|
|
10790
|
+
never garbled); malformed-JSON retries keep the deterministic ladder
|
|
10791
|
+
because structure repair wants determinism.
|
|
10792
|
+
"""
|
|
10793
|
+
if is_garbled and attempt >= 3:
|
|
10794
|
+
return max(
|
|
10795
|
+
PROXY_ATTRACTOR_TEMP_OVERRIDE, PROXY_MALFORMED_TOOL_RETRY_TEMPERATURE
|
|
10796
|
+
)
|
|
10797
|
+
if attempt > 1:
|
|
10798
|
+
return 0.5
|
|
10799
|
+
return PROXY_MALFORMED_TOOL_RETRY_TEMPERATURE
|
|
10800
|
+
|
|
10801
|
+
|
|
10802
|
+
def _contamination_reset_cause(monitor: "SessionMonitor") -> str:
|
|
10803
|
+
"""Dominant output-quality streak behind a contamination reset.
|
|
10804
|
+
|
|
10805
|
+
Drives the post-reset temperature policy: degenerate-content faults
|
|
10806
|
+
(invalid_tool_args) are EXEMPT from the 0.10 low-temp clamp (see
|
|
10807
|
+
PROXY_CONTAMINATION_LOW_TEMP_EXEMPT_KINDS) because determinism re-enters
|
|
10808
|
+
the same attractor; malformed-JSON repairs keep the clamp.
|
|
10809
|
+
"""
|
|
10810
|
+
if monitor.invalid_tool_call_streak > monitor.malformed_tool_streak:
|
|
10811
|
+
return "invalid_tool_args"
|
|
10812
|
+
if monitor.malformed_tool_streak > 0:
|
|
10813
|
+
return "malformed_payload"
|
|
10814
|
+
if monitor.required_tool_miss_streak > 0:
|
|
10815
|
+
return "required_tool_miss"
|
|
10816
|
+
return "generic"
|
|
10817
|
+
|
|
10818
|
+
|
|
10720
10819
|
def _extract_openai_tool_calls(openai_resp: dict) -> list[dict]:
|
|
10721
10820
|
_, message = _extract_openai_choice(openai_resp)
|
|
10722
10821
|
tool_calls = message.get("tool_calls") or []
|
|
@@ -12399,10 +12498,20 @@ def _validate_tool_call_arguments(
|
|
|
12399
12498
|
allowed_tools: set[str],
|
|
12400
12499
|
) -> ToolResponseIssue:
|
|
12401
12500
|
if allowed_tools and tool_name not in allowed_tools:
|
|
12501
|
+
# 2026-10-30: name the kept alternatives — a narrowed retry (the
|
|
12502
|
+
# garbling tool excluded) observed the model emitting the excluded
|
|
12503
|
+
# tool anyway because "use the provided tool list" gave it nothing
|
|
12504
|
+
# concrete to switch to.
|
|
12505
|
+
known = ", ".join(sorted(allowed_tools)[:6])
|
|
12402
12506
|
return ToolResponseIssue(
|
|
12403
12507
|
kind="invalid_tool_args",
|
|
12404
12508
|
reason=f"unknown tool '{tool_name}'",
|
|
12405
|
-
retry_hint=
|
|
12509
|
+
retry_hint=(
|
|
12510
|
+
f"'{tool_name}' is not available on this turn. "
|
|
12511
|
+
f"Call exactly one of: {known}."
|
|
12512
|
+
if known
|
|
12513
|
+
else "Use exactly one tool from the provided tool list."
|
|
12514
|
+
),
|
|
12406
12515
|
)
|
|
12407
12516
|
|
|
12408
12517
|
if isinstance(raw_arguments, (dict, list)):
|
|
@@ -12474,7 +12583,10 @@ def _validate_tool_call_arguments(
|
|
|
12474
12583
|
reason=f"arguments for '{tool_name}' contain garbled/degenerate content",
|
|
12475
12584
|
retry_hint=(
|
|
12476
12585
|
f"Emit exactly one `{tool_name}` tool call with well-formed JSON arguments. "
|
|
12477
|
-
"
|
|
12586
|
+
"Your previous arguments degenerated (repeated digit or brace runs). "
|
|
12587
|
+
"SIMPLIFY and SHORTEN the operation: split the work into smaller steps, "
|
|
12588
|
+
"avoid long inline heredocs and large generated numeric blocks, and "
|
|
12589
|
+
"prefer short commands. Do not repeat closing braces, brackets, or digits."
|
|
12478
12590
|
),
|
|
12479
12591
|
)
|
|
12480
12592
|
|
|
@@ -13053,12 +13165,16 @@ def _build_malformed_retry_body(
|
|
|
13053
13165
|
retry_body["stream"] = False
|
|
13054
13166
|
retry_body["tool_choice"] = tool_choice
|
|
13055
13167
|
# Cycle 15 Option 3: vary temperature across retries to break degenerate patterns.
|
|
13056
|
-
#
|
|
13057
|
-
#
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13168
|
+
# Ladder lives in _retry_temperature (garbled retries escalate to the
|
|
13169
|
+
# attractor-escape temperature from attempt 3 — see its docstring).
|
|
13170
|
+
retry_body["temperature"] = _retry_temperature(attempt, is_garbled)
|
|
13171
|
+
if is_garbled and attempt >= 3 and PROXY_GARBLE_LATE_CAP > 0:
|
|
13172
|
+
logger.info(
|
|
13173
|
+
"RETRY GARBLED ESCALATION: temperature=%.2f late_cap=%d for garbled retry attempt=%d",
|
|
13174
|
+
retry_body["temperature"],
|
|
13175
|
+
PROXY_GARBLE_LATE_CAP,
|
|
13176
|
+
attempt,
|
|
13177
|
+
)
|
|
13062
13178
|
|
|
13063
13179
|
if tool_choice == "required":
|
|
13064
13180
|
retry_instruction = (
|
|
@@ -13090,7 +13206,17 @@ def _build_malformed_retry_body(
|
|
|
13090
13206
|
|
|
13091
13207
|
# Option 1: Progressive garbled-cap within retries — use smaller max_tokens
|
|
13092
13208
|
# when the issue involves garbled/degenerate args to limit degeneration room.
|
|
13093
|
-
|
|
13209
|
+
# 2026-10-30: the deployed env pins PROXY_TOOL_TURN_MAX_TOKENS_GARBLED high
|
|
13210
|
+
# (32768) so legit multi-file heredoc edits don't truncate (2026-04-22) —
|
|
13211
|
+
# from attempt 3 onward, starve degeneration room with the late cap.
|
|
13212
|
+
if is_garbled and attempt >= 3 and PROXY_GARBLE_LATE_CAP > 0:
|
|
13213
|
+
retry_body["max_tokens"] = PROXY_GARBLE_LATE_CAP
|
|
13214
|
+
logger.info(
|
|
13215
|
+
"RETRY GARBLED LATE CAP: max_tokens=%d for garbled retry attempt=%d",
|
|
13216
|
+
PROXY_GARBLE_LATE_CAP,
|
|
13217
|
+
attempt,
|
|
13218
|
+
)
|
|
13219
|
+
elif is_garbled and PROXY_TOOL_TURN_MAX_TOKENS_GARBLED > 0:
|
|
13094
13220
|
retry_body["max_tokens"] = PROXY_TOOL_TURN_MAX_TOKENS_GARBLED
|
|
13095
13221
|
logger.info(
|
|
13096
13222
|
"RETRY GARBLED CAP: max_tokens=%d for garbled retry attempt=%d",
|
|
@@ -13435,17 +13561,22 @@ async def _apply_malformed_tool_guardrail(
|
|
|
13435
13561
|
excerpt = _openai_message_text(working_resp)[:220].replace("\n", " ")
|
|
13436
13562
|
# Option 2: Log garbled argument content for diagnostics
|
|
13437
13563
|
arg_excerpt = ""
|
|
13564
|
+
fault_tool_names: list[str] = []
|
|
13438
13565
|
if issue.kind == "invalid_tool_args":
|
|
13439
13566
|
for tc in (working_resp.get("choices", [{}])[0].get("message", {}).get("tool_calls") or []):
|
|
13567
|
+
fn_name = tc.get("function", {}).get("name", "")
|
|
13440
13568
|
raw_args = tc.get("function", {}).get("arguments", "")
|
|
13441
|
-
if
|
|
13569
|
+
if fn_name:
|
|
13570
|
+
fault_tool_names.append(fn_name)
|
|
13571
|
+
if raw_args and _is_garbled_tool_arguments(raw_args) and not arg_excerpt:
|
|
13442
13572
|
arg_excerpt = raw_args[:200].replace("\n", " ")
|
|
13443
|
-
break
|
|
13444
13573
|
# Attractor detection — hash the normalized fault excerpt so the
|
|
13445
13574
|
# contamination breaker can recognize the same fixed-point response
|
|
13446
13575
|
# reappearing across consecutive resets. Whitespace-normalized so trivial
|
|
13447
13576
|
# rendering differences don't break the match.
|
|
13448
|
-
monitor.last_fault_excerpt_hash =
|
|
13577
|
+
monitor.last_fault_excerpt_hash = _fault_fingerprint(
|
|
13578
|
+
excerpt, arg_excerpt, fault_tool_names
|
|
13579
|
+
)
|
|
13449
13580
|
logger.warning(
|
|
13450
13581
|
"TOOL RESPONSE ISSUE: session=%s kind=%s reason=%s malformed=%d invalid=%d required_miss=%d excerpt=%.220s args=%.200s",
|
|
13451
13582
|
session_id,
|
|
@@ -13801,6 +13932,7 @@ def _maybe_apply_session_contamination_breaker(
|
|
|
13801
13932
|
max_contamination_resets,
|
|
13802
13933
|
)
|
|
13803
13934
|
monitor.set_tool_turn_phase("finalize", reason="contamination_loop")
|
|
13935
|
+
monitor.contamination_cause = _contamination_reset_cause(monitor)
|
|
13804
13936
|
monitor.contamination_resets += 1
|
|
13805
13937
|
monitor.malformed_tool_streak = 0
|
|
13806
13938
|
monitor.invalid_tool_call_streak = 0
|
|
@@ -13923,6 +14055,7 @@ def _maybe_apply_session_contamination_breaker(
|
|
|
13923
14055
|
|
|
13924
14056
|
forced_before = monitor.consecutive_forced_count
|
|
13925
14057
|
required_miss_before = monitor.required_tool_miss_streak
|
|
14058
|
+
monitor.contamination_cause = _contamination_reset_cause(monitor)
|
|
13926
14059
|
monitor.contamination_resets += 1
|
|
13927
14060
|
# Emergency tier: the conversation itself is rewritten, so BOTH the
|
|
13928
14061
|
# forcing-loop family (via its one policy point) and the output-quality
|