@miller-tech/uap 1.88.0 → 1.89.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.88.0",
3
+ "version": "1.89.0",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -42,6 +42,7 @@ class Settings:
42
42
  signal: str
43
43
  threshold: float
44
44
  fusion_n: int
45
+ remom_quorum: int
45
46
  auto_fusion_chars: int
46
47
  model: str
47
48
  endpoint: str
@@ -59,7 +60,7 @@ class Settings:
59
60
  return default
60
61
 
61
62
  recipe = os.environ.get("PROXY_RECIPE", "auto").lower()
62
- if recipe not in {"auto", "single", "confidence", "fusion"}:
63
+ if recipe not in {"auto", "single", "confidence", "fusion", "ratings", "remom", "workflow"}:
63
64
  recipe = "auto"
64
65
  signal = os.environ.get("PROXY_CONFIDENCE_SIGNAL", "heuristic").lower()
65
66
  if signal not in {"heuristic", "selfverify"}:
@@ -70,6 +71,7 @@ class Settings:
70
71
  signal=signal,
71
72
  threshold=num("PROXY_CONFIDENCE_THRESHOLD", 0.5, float),
72
73
  fusion_n=max(2, min(6, num("PROXY_FUSION_N", 3, int))),
74
+ remom_quorum=max(1, min(6, num("PROXY_REMOM_QUORUM", 2, int))),
73
75
  auto_fusion_chars=num("PROXY_AUTO_FUSION_CHARS", 600, int),
74
76
  model=os.environ.get("PROXY_ESCALATE_MODEL", ""),
75
77
  endpoint=os.environ.get("PROXY_ESCALATE_ENDPOINT", ""),
@@ -321,6 +323,26 @@ def parse_judge_index(text: str, n: int):
321
323
  return i if 0 <= i < n else None
322
324
 
323
325
 
326
+ def build_synthesis_payload(anthropic_body: dict, candidate_texts: list[str], settings: Settings) -> dict:
327
+ q = latest_user_text(anthropic_body)
328
+ listing = "\n\n".join(f"[{i}]\n{t}" for i, t in enumerate(candidate_texts))
329
+ prompt = (
330
+ "Synthesize a SINGLE best answer to the REQUEST by merging the correct, "
331
+ "complementary parts of the candidate answers below. Resolve disagreements "
332
+ "and keep the required output format. Reply with ONLY the final answer."
333
+ "\n\nREQUEST:\n" + q + "\n\nCANDIDATES:\n" + listing
334
+ )
335
+ return {"model": settings.model, "max_tokens": anthropic_body.get("max_tokens", 4096),
336
+ "messages": [{"role": "user", "content": prompt}]}
337
+
338
+
339
+ async def _fanout_candidates(primary_resp, openai_body, settings, call_primary):
340
+ """[primary] + up to fusion_n-1 temperature-varied breadth candidates."""
341
+ variants = build_fusion_variants(openai_body or {}, settings.fusion_n)
342
+ results = await asyncio.gather(*[call_primary(v) for v in variants], return_exceptions=True)
343
+ return [primary_resp] + [r for r in results if isinstance(r, dict)]
344
+
345
+
324
346
  def should_escalate(text: str, settings: Settings, has_tools: bool) -> bool:
325
347
  """Back-compat (heuristic confidence path)."""
326
348
  if not settings.enabled or not settings.backend_configured() or has_tools:
@@ -370,6 +392,42 @@ async def apply_recipe(primary_resp, anthropic_body, openai_body, settings, has_
370
392
  if idx is not None:
371
393
  return candidates[idx]
372
394
  return primary_resp # fallback to best valid evidence
395
+
396
+ if recipe == "ratings":
397
+ # Bounded ensemble: rate each candidate independently, pick the best.
398
+ candidates = await _fanout_candidates(primary_resp, openai_body, settings, call_primary)
399
+ if len(candidates) <= 1 or not settings.backend_configured() or call_judge is None:
400
+ return primary_resp
401
+ rs = await asyncio.gather(
402
+ *[call_judge(build_verify_payload(anthropic_body, extract_text(c), settings))
403
+ for c in candidates],
404
+ return_exceptions=True,
405
+ )
406
+ scored = []
407
+ for c, jr2 in zip(candidates, rs):
408
+ sc = parse_verify_score(extract_text(jr2)) if isinstance(jr2, dict) else None
409
+ scored.append((sc if sc is not None else text_confidence(extract_text(c)), c))
410
+ return max(scored, key=lambda x: x[0])[1]
411
+
412
+ if recipe == "remom":
413
+ # Breadth -> quorum -> synthesis into the output contract; fall back
414
+ # to the best valid evidence if synthesis fails (no API error).
415
+ candidates = await _fanout_candidates(primary_resp, openai_body, settings, call_primary)
416
+ valid = [c for c in candidates if extract_text(c).strip()]
417
+ if len(valid) >= settings.remom_quorum and settings.backend_configured() and call_judge is not None:
418
+ synth = await call_judge(
419
+ build_synthesis_payload(anthropic_body, [extract_text(c) for c in valid], settings)
420
+ )
421
+ if isinstance(synth, dict) and extract_text(synth).strip():
422
+ return synth
423
+ return max(valid, key=lambda c: len(extract_text(c))) if valid else primary_resp
424
+
425
+ if recipe == "workflow":
426
+ # Workflows (planner/patcher/verifier under a contract) are the
427
+ # deliver convergence loop's job — it owns the real execution +
428
+ # acceptance gates and repo state a stateless serving turn lacks.
429
+ # Pass through; the harness routes workflow tasks through uap deliver.
430
+ return primary_resp
373
431
  except Exception:
374
432
  return primary_resp
375
433
  return primary_resp
@@ -15,10 +15,10 @@ spec.loader.exec_module(ce)
15
15
 
16
16
 
17
17
  def S(enabled=True, recipe="auto", signal="heuristic", threshold=0.5, fusion_n=3,
18
- auto_chars=600, model="opus", endpoint="http://x/", key="k"):
18
+ auto_chars=600, model="opus", endpoint="http://x/", key="k", remom_quorum=2):
19
19
  return ce.Settings(enabled=enabled, recipe=recipe, signal=signal, threshold=threshold,
20
- fusion_n=fusion_n, auto_fusion_chars=auto_chars, model=model,
21
- endpoint=endpoint, api_key=key)
20
+ fusion_n=fusion_n, remom_quorum=remom_quorum, auto_fusion_chars=auto_chars,
21
+ model=model, endpoint=endpoint, api_key=key)
22
22
 
23
23
 
24
24
  def resp(text):
@@ -180,5 +180,56 @@ class CrossProcessSignalTest(unittest.TestCase): # reactor -> proxy
180
180
  self.assertEqual(r, "fusion") # reactor signal overrides the simple self-classification
181
181
 
182
182
 
183
+
184
+ class ApplyRatingsTest(unittest.TestCase): # Ratings
185
+ def test_picks_highest_rated_candidate(self):
186
+ async def primary(v): return resp("cand-1")
187
+ scores = iter(["3", "9"]) # primary rated 3, fanout rated 9
188
+ async def judge(p): return resp(next(scores))
189
+ out = run(ce.apply_recipe(resp("cand-0"), body(), {"model": "q", "messages": []},
190
+ S(recipe="ratings", fusion_n=2), False, primary, judge))
191
+ self.assertEqual(ce.extract_text(out), "cand-1")
192
+
193
+
194
+ class ApplyReMoMTest(unittest.TestCase): # ReMoM
195
+ def test_synthesizes_when_quorum_met(self):
196
+ async def primary(v): return resp("evidence B")
197
+ async def judge(p): return resp("MERGED ANSWER")
198
+ out = run(ce.apply_recipe(resp("evidence A"), body(), {"model": "q", "messages": []},
199
+ S(recipe="remom", fusion_n=2), False, primary, judge))
200
+ self.assertEqual(ce.extract_text(out), "MERGED ANSWER")
201
+
202
+ def test_falls_back_to_best_evidence_when_synthesis_fails(self):
203
+ async def primary(v): return resp("a longer, more complete candidate answer")
204
+ async def judge(p): return None # synthesis failed
205
+ out = run(ce.apply_recipe(resp("short"), body(), {"model": "q", "messages": []},
206
+ S(recipe="remom", fusion_n=2), False, primary, judge))
207
+ self.assertEqual(ce.extract_text(out), "a longer, more complete candidate answer")
208
+
209
+
210
+ class WorkflowTest(unittest.TestCase):
211
+ def test_workflow_passes_through(self): # Workflows = deliver's job
212
+ async def primary(v): return resp("x")
213
+ async def judge(p): return resp("y")
214
+ out = run(ce.apply_recipe(resp("PRIMARY"), body(), {}, S(recipe="workflow"), False, primary, judge))
215
+ self.assertEqual(ce.extract_text(out), "PRIMARY")
216
+
217
+
218
+ class SettingsExtTest(unittest.TestCase):
219
+ def test_remom_quorum_default(self):
220
+ import os, tempfile
221
+ for k in ("PROXY_REMOM_QUORUM",): os.environ.pop(k, None)
222
+ self.assertEqual(ce.Settings.from_env().remom_quorum, 2)
223
+
224
+ def test_new_recipes_valid(self):
225
+ import os
226
+ for r in ("ratings", "remom", "workflow"):
227
+ os.environ["PROXY_RECIPE"] = r
228
+ try:
229
+ self.assertEqual(ce.Settings.from_env().recipe, r)
230
+ finally:
231
+ os.environ.pop("PROXY_RECIPE", None)
232
+
233
+
183
234
  if __name__ == "__main__":
184
235
  unittest.main()