@miller-tech/uap 1.222.0 → 2.1.4

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.
Files changed (234) hide show
  1. package/README.md +19 -9
  2. package/config/exl3-profiles/qwen38-27b-exl3.env +68 -0
  3. package/config/llama-profiles/qwen38-27b-dflash2.env +6 -1
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/benchmarks/paired/adapter.d.ts +15 -0
  6. package/dist/benchmarks/paired/adapter.d.ts.map +1 -1
  7. package/dist/benchmarks/paired/adapter.js +22 -1
  8. package/dist/benchmarks/paired/adapter.js.map +1 -1
  9. package/dist/bin/cli.js +20 -24
  10. package/dist/bin/cli.js.map +1 -1
  11. package/dist/cli/deliver.d.ts +34 -0
  12. package/dist/cli/deliver.d.ts.map +1 -1
  13. package/dist/cli/deliver.js +147 -20
  14. package/dist/cli/deliver.js.map +1 -1
  15. package/dist/cli/guided-setup.d.ts.map +1 -1
  16. package/dist/cli/guided-setup.js +15 -0
  17. package/dist/cli/guided-setup.js.map +1 -1
  18. package/dist/cli/init.d.ts.map +1 -1
  19. package/dist/cli/init.js +3 -0
  20. package/dist/cli/init.js.map +1 -1
  21. package/dist/cli/plan.d.ts.map +1 -1
  22. package/dist/cli/plan.js +8 -2
  23. package/dist/cli/plan.js.map +1 -1
  24. package/dist/cli/policy.d.ts.map +1 -1
  25. package/dist/cli/policy.js +104 -2
  26. package/dist/cli/policy.js.map +1 -1
  27. package/dist/cli/quality.d.ts +11 -0
  28. package/dist/cli/quality.d.ts.map +1 -0
  29. package/dist/cli/quality.js +213 -0
  30. package/dist/cli/quality.js.map +1 -0
  31. package/dist/cli/wizard-config.d.ts.map +1 -1
  32. package/dist/cli/wizard-config.js +19 -9
  33. package/dist/cli/wizard-config.js.map +1 -1
  34. package/dist/cli/worktree.d.ts.map +1 -1
  35. package/dist/cli/worktree.js +8 -1
  36. package/dist/cli/worktree.js.map +1 -1
  37. package/dist/dashboard/savings.d.ts +1 -4
  38. package/dist/dashboard/savings.d.ts.map +1 -1
  39. package/dist/dashboard/savings.js +2 -52
  40. package/dist/dashboard/savings.js.map +1 -1
  41. package/dist/delivery/acceptance-judge.d.ts +10 -0
  42. package/dist/delivery/acceptance-judge.d.ts.map +1 -1
  43. package/dist/delivery/acceptance-judge.js +45 -3
  44. package/dist/delivery/acceptance-judge.js.map +1 -1
  45. package/dist/delivery/agentic-executor.d.ts +12 -1
  46. package/dist/delivery/agentic-executor.d.ts.map +1 -1
  47. package/dist/delivery/agentic-executor.js +25 -5
  48. package/dist/delivery/agentic-executor.js.map +1 -1
  49. package/dist/delivery/bash-sweep.d.ts +12 -0
  50. package/dist/delivery/bash-sweep.d.ts.map +1 -1
  51. package/dist/delivery/bash-sweep.js +8 -1
  52. package/dist/delivery/bash-sweep.js.map +1 -1
  53. package/dist/delivery/convergence-loop.d.ts.map +1 -1
  54. package/dist/delivery/convergence-loop.js +6 -4
  55. package/dist/delivery/convergence-loop.js.map +1 -1
  56. package/dist/delivery/execution-gate.d.ts +1 -1
  57. package/dist/delivery/execution-gate.d.ts.map +1 -1
  58. package/dist/delivery/execution-gate.js +248 -5
  59. package/dist/delivery/execution-gate.js.map +1 -1
  60. package/dist/delivery/snapshot.d.ts +16 -0
  61. package/dist/delivery/snapshot.d.ts.map +1 -1
  62. package/dist/delivery/snapshot.js +113 -2
  63. package/dist/delivery/snapshot.js.map +1 -1
  64. package/dist/delivery/user-validation.d.ts.map +1 -1
  65. package/dist/delivery/user-validation.js +55 -10
  66. package/dist/delivery/user-validation.js.map +1 -1
  67. package/dist/delivery/verifier-ladder.d.ts +24 -0
  68. package/dist/delivery/verifier-ladder.d.ts.map +1 -1
  69. package/dist/delivery/verifier-ladder.js +119 -2
  70. package/dist/delivery/verifier-ladder.js.map +1 -1
  71. package/dist/generators/claude-md.js +17 -4
  72. package/dist/generators/claude-md.js.map +1 -1
  73. package/dist/policies/liveness.d.ts +105 -0
  74. package/dist/policies/liveness.d.ts.map +1 -0
  75. package/dist/policies/liveness.js +251 -0
  76. package/dist/policies/liveness.js.map +1 -0
  77. package/dist/policies/policy-memory.d.ts.map +1 -1
  78. package/dist/policies/policy-memory.js +3 -4
  79. package/dist/policies/policy-memory.js.map +1 -1
  80. package/dist/quality/baseline.d.ts +29 -0
  81. package/dist/quality/baseline.d.ts.map +1 -0
  82. package/dist/quality/baseline.js +79 -0
  83. package/dist/quality/baseline.js.map +1 -0
  84. package/dist/quality/complexity.d.ts +38 -0
  85. package/dist/quality/complexity.d.ts.map +1 -0
  86. package/dist/quality/complexity.js +239 -0
  87. package/dist/quality/complexity.js.map +1 -0
  88. package/dist/quality/config.d.ts +40 -0
  89. package/dist/quality/config.d.ts.map +1 -0
  90. package/dist/quality/config.js +77 -0
  91. package/dist/quality/config.js.map +1 -0
  92. package/dist/quality/crap.d.ts +11 -0
  93. package/dist/quality/crap.d.ts.map +1 -0
  94. package/dist/quality/crap.js +54 -0
  95. package/dist/quality/crap.js.map +1 -0
  96. package/dist/quality/languages.d.ts +25 -0
  97. package/dist/quality/languages.d.ts.map +1 -0
  98. package/dist/quality/languages.js +150 -0
  99. package/dist/quality/languages.js.map +1 -0
  100. package/dist/quality/report.d.ts +38 -0
  101. package/dist/quality/report.d.ts.map +1 -0
  102. package/dist/quality/report.js +251 -0
  103. package/dist/quality/report.js.map +1 -0
  104. package/dist/quality/scanner.d.ts +32 -0
  105. package/dist/quality/scanner.d.ts.map +1 -0
  106. package/dist/quality/scanner.js +127 -0
  107. package/dist/quality/scanner.js.map +1 -0
  108. package/dist/quality/tools.d.ts +28 -0
  109. package/dist/quality/tools.d.ts.map +1 -0
  110. package/dist/quality/tools.js +189 -0
  111. package/dist/quality/tools.js.map +1 -0
  112. package/dist/types/config.d.ts +148 -0
  113. package/dist/types/config.d.ts.map +1 -1
  114. package/dist/types/config.js +35 -0
  115. package/dist/types/config.js.map +1 -1
  116. package/docs/ATTRIBUTION.md +0 -1
  117. package/docs/INDEX.md +38 -4
  118. package/docs/analysis/MARKET_POSITION.md +179 -0
  119. package/docs/architecture/OVERVIEW.md +82 -18
  120. package/docs/architecture/PROTOCOL.md +1 -1
  121. package/docs/benchmarks/PAIRED_FINDINGS.md +106 -0
  122. package/docs/benchmarks/PAIRED_HARNESS.md +16 -0
  123. package/docs/benchmarks/README.md +59 -0
  124. package/docs/design/UAP_REACTOR.md +0 -1
  125. package/docs/getting-started/INSTALLATION.md +1 -1
  126. package/docs/guides/AUTOMATIC.md +0 -14
  127. package/docs/guides/AUTOMATIC_FEATURES.md +1 -9
  128. package/docs/guides/COORDINATION.md +1 -1
  129. package/docs/guides/DELIVERY_PIPELINE.md +5 -0
  130. package/docs/guides/DEPLOY_BATCHING.md +1 -1
  131. package/docs/guides/DROIDS_AND_SKILLS.md +1 -1
  132. package/docs/guides/LOCAL_MODELS.md +6 -1
  133. package/docs/guides/MCP_ROUTER.md +1 -1
  134. package/docs/guides/MEMORY.md +1 -1
  135. package/docs/guides/MULTI_MODEL.md +1 -1
  136. package/docs/guides/ORCHESTRATOR.md +1 -1
  137. package/docs/guides/POLICIES.md +19 -3
  138. package/docs/guides/QWEN38_EXL3.md +114 -0
  139. package/docs/guides/WORKTREE_WORKFLOW.md +45 -1
  140. package/docs/integrations/MCP_ROUTER.md +1 -4
  141. package/docs/plans/deliver-hardening-plan-2026-07-13.md +173 -0
  142. package/docs/plans/deliver-hardening-review-response-2026-07-13.md +180 -0
  143. package/docs/plans/k3-uap-uplift-validation.md +216 -0
  144. package/docs/plans/qwen38-uap-retest-plan.md +196 -0
  145. package/docs/reference/API.md +1 -1
  146. package/docs/reference/CLI.md +49 -14
  147. package/docs/reference/COMPARISON.md +248 -0
  148. package/docs/reference/CONFIGURATION.md +1 -3
  149. package/docs/reference/DATABASE_SCHEMA.md +1 -1
  150. package/docs/reference/FEATURES.md +3 -3
  151. package/docs/reference/PATTERNS.md +1 -1
  152. package/docs/reference/PLATFORMS.md +1 -1
  153. package/package.json +2 -2
  154. package/scripts/maintenance/update-skills.py +0 -22
  155. package/scripts/maintenance/verify-compliance.sh +3 -3
  156. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  157. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
  158. package/src/policies/enforcers/adr_guard.py +0 -0
  159. package/src/policies/enforcers/bearer_lockdown.py +0 -0
  160. package/src/policies/enforcers/branch_freshness.py +0 -0
  161. package/src/policies/enforcers/design_token_gate.py +0 -0
  162. package/src/policies/enforcers/enforcement_self_protect.py +70 -0
  163. package/src/policies/enforcers/expert_review_required.py +22 -0
  164. package/src/policies/enforcers/iac_plan_destruction_check.py +0 -0
  165. package/src/policies/enforcers/quality_metrics_gate.py +447 -0
  166. package/src/policies/enforcers/ship_loop_gate.py +0 -0
  167. package/src/policies/enforcers/validate_plan_on_change.py +0 -0
  168. package/src/policies/enforcers/visual_verification.py +0 -0
  169. package/src/policies/enforcers/workdir_scope.py +0 -0
  170. package/src/policies/schemas/policies/expert-review-required.md +9 -1
  171. package/src/policies/schemas/policies/quality-metrics-gate.md +74 -0
  172. package/templates/CLAUDE.template.md +1 -1
  173. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  174. package/templates/hooks/pre-tool-use-bash.sh +50 -3
  175. package/templates/hooks/pre-tool-use-edit-write.sh +31 -0
  176. package/templates/hooks/session-start.sh +15 -0
  177. package/templates/hooks/uap-policy-gate.sh +187 -5
  178. package/templates/stryker.config.json +14 -0
  179. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  180. package/tools/agents/scripts/anthropic_proxy.py +254 -28
  181. package/tools/agents/tests/run_acceptance.py +0 -0
  182. package/tools/agents/tests/test_client_disconnect.py +0 -0
  183. package/tools/agents/tests/test_cycle_break_exploration.py +0 -0
  184. package/tools/agents/tests/test_deferral_break.py +0 -0
  185. package/tools/agents/tests/test_delivery_enforcement_all_langs.py +0 -0
  186. package/tools/agents/tests/test_delivery_enforcement_exemptions.py +0 -0
  187. package/tools/agents/tests/test_delivery_enforcement_filepath.py +0 -0
  188. package/tools/agents/tests/test_delivery_enforcement_web_and_bash.py +0 -0
  189. package/tools/agents/tests/test_delivery_enforcement_worktree.py +0 -0
  190. package/tools/agents/tests/test_delivery_enforcement_write_complexity.py +0 -0
  191. package/tools/agents/tests/test_disconnect_watcher.py +0 -0
  192. package/tools/agents/tests/test_doubling_break.py +0 -0
  193. package/tools/agents/tests/test_empty_maxtokens_recovery.py +0 -0
  194. package/tools/agents/tests/test_empty_tool_loop_break.py +0 -0
  195. package/tools/agents/tests/test_enforcement_self_protect.py +90 -0
  196. package/tools/agents/tests/test_enforcer_escape_hatches.py +0 -0
  197. package/tools/agents/tests/test_error_loop_break.py +0 -0
  198. package/tools/agents/tests/test_expert_review_pr_scope.py +13 -5
  199. package/tools/agents/tests/test_fastpath_config.py +0 -0
  200. package/tools/agents/tests/test_finalize_suppression.py +0 -0
  201. package/tools/agents/tests/test_gate_evidence.py +0 -0
  202. package/tools/agents/tests/test_gate_failclosed_indirection.py +0 -0
  203. package/tools/agents/tests/test_gate_integrity.py +0 -0
  204. package/tools/agents/tests/test_live_deliver_kill.py +0 -0
  205. package/tools/agents/tests/test_malformed_unclosed_think.py +0 -0
  206. package/tools/agents/tests/test_mandate_beats_recon.py +0 -0
  207. package/tools/agents/tests/test_mandate_deliver.py +0 -0
  208. package/tools/agents/tests/test_non_stream_resp_log.py +0 -0
  209. package/tools/agents/tests/test_output_token_ceilings.py +0 -0
  210. package/tools/agents/tests/test_overflow_truncate_count_tokens.py +0 -0
  211. package/tools/agents/tests/test_path_containment.py +0 -0
  212. package/tools/agents/tests/test_path_normalizer_hardened.py +0 -0
  213. package/tools/agents/tests/test_repeat_call_guard.py +0 -0
  214. package/tools/agents/tests/test_repeat_guard.py +0 -0
  215. package/tools/agents/tests/test_session_admission.py +0 -0
  216. package/tools/agents/tests/test_stream_heartbeat.py +0 -0
  217. package/tools/agents/tests/test_stream_thinking_deltas.py +405 -0
  218. package/tools/agents/tests/test_stuck_break_hard.py +0 -0
  219. package/tools/agents/tests/test_stuck_break_reattach.py +0 -0
  220. package/tools/agents/tests/test_turn_count_breaker_periodic.py +0 -0
  221. package/tools/agents/tests/test_upstream_chokepoint.py +0 -0
  222. package/tools/agents/tests/test_upstream_model_aliases.py +0 -0
  223. package/tools/agents/tests/test_validate_plan_gate.py +0 -0
  224. package/tools/agents/tests/test_vision_passthrough.py +0 -0
  225. package/tools/agents/tests/test_workdir_scope_enforcer.py +0 -0
  226. package/dist/cli/rtk.d.ts +0 -34
  227. package/dist/cli/rtk.d.ts.map +0 -1
  228. package/dist/cli/rtk.js +0 -401
  229. package/dist/cli/rtk.js.map +0 -1
  230. package/docs/integrations/RTK.md +0 -113
  231. package/scripts/setup/install-rtk.sh +0 -184
  232. package/src/policies/enforcers/rtk_wrap.py +0 -205
  233. package/src/policies/schemas/policies/rtk-wrap.md +0 -50
  234. package/tools/agents/tests/test_rtk_wrap.py +0 -151
@@ -1,6 +1,6 @@
1
1
  # MCP Router
2
2
 
3
- `v1.93.1` · `src/mcp-router/`
3
+ `v1.224.0` · `src/mcp-router/`
4
4
 
5
5
  > **🏭 Where this fits:** Cross-cutting (keeps the belt from jamming) — every
6
6
  > station downstream stalls when your agent's context window is choked with
@@ -151,6 +151,3 @@ router's view, not deleted) and re-run `uap mcp-setup`.
151
151
  - The 98% / 75k→700 figures are the documented design target for tool hiding;
152
152
  per-output FTS5 savings are computed live for each call and reported by
153
153
  `uap mcp-router stats`.
154
- - Pair the router with **RTK** for CLI-output savings — see
155
- [RTK.md](RTK.md). The two are complementary (tool definitions + CLI output),
156
- and together they keep the whole belt clear of context clutter.
@@ -0,0 +1,173 @@
1
+ # Deliver/enforcement hardening plan — 2026-07-13
2
+
3
+ Motivating incident (pay2u, 2026-07-13): the `delivery-enforcement` policy blocked
4
+ direct source edits and routed a pre-reviewed 6-file C++ change into `uap deliver`.
5
+ The run declared **"Delivered — all required gates pass" after writing ZERO files**:
6
+ the only detected project gates were npm build/test for `apps/web`, which were
7
+ already green, so the loop had nothing that could fail. Meanwhile every blocked
8
+ edit auto-spawned a **blind** background deliver run whose mission was the literal
9
+ string "implement the intended change to <file>" — no content, no spec.
10
+ Related, earlier incident (same repo, board finding): `expert-review-required`
11
+ policy demanded artifacts under paths that Enforcement-Self-Protect locks agents
12
+ out of, while the skill it named did not exist — a compliance catch-22.
13
+
14
+ Defect sites (all paths relative to this repo):
15
+
16
+ | # | Defect | Where |
17
+ |---|--------|-------|
18
+ | 1 | "Delivered" = required gates green; when gates are green at (or near) baseline the loop succeeds without a diff. Two paths: the `baselineCheck` short-circuit (`alreadyDelivered`) and the normal ladder pass — the incident run went green via the latter in 2 turns with zero writes | `src/delivery/convergence-loop.ts` ~742 (`baselineCheck`) and ladder success path |
19
+ | 2 | Gate relevance to the MISSION is never established — npm web gates "verified" a C++ handler change | `src/cli/deliver.ts` gate selection, `src/delivery/execution-gate.ts` |
20
+ | 3 | Self-gate (anti-vacuous floor) only engages when NO project gates are detected | `src/cli/deliver.ts` ~43 (`needsSelfGate = … noRealGates \|\| forceSelfGate`) |
21
+ | 4 | `--no-auto` silently disables the acceptance judge (a verification rail) along with exploration aids | `src/cli/deliver.ts` auto-plan wiring |
22
+ | 5 | Acceptance judge can accept with an empty diff (diff-blind) | `src/delivery/acceptance-judge.ts` |
23
+ | 6 | Gate detection is npm-centric; polyglot repos (C++/docker, SQL migrations) get irrelevant gates | `src/delivery/execution-gate.ts` (`detectArtifactType`, package.json only) |
24
+ | 7 | Autoroute spawns deliver with a vacuous hint; the blocked edit's actual content (old/new strings) is discarded | `src/policies/enforcers/delivery_enforcement.py` ~158 (`deliverHint=f'implement the intended change to {rel}'`), `templates/hooks/deliver_autoroute.py` |
25
+ | 8 | Agentic executor has whole-file `write_file` only — no anchored edit tool; weak models re-read in loops and whole-file rewrites of 1,200-line files are high-risk | `src/delivery/agentic-executor.ts` |
26
+ | 9 | keep-best / protect-tests snapshots cover the whole tree — a deliver run in a SHARED worktree reverts other agents' concurrent (legitimate) edits, including their newly written tests (observed: frontend agent's new test file reverted) | `src/delivery/…` snapshot/rollback + protect-tests paths |
27
+ | 10 | Policy liveness: a policy can demand a compliant path that does not work (missing skill, agent-locked artifact dir, deliver that no-ops) with no detection | `src/policies/*` (no liveness concept) |
28
+ | 11 | Inverted self-protect: compliance artifact paths locked, but the policies DB the gate reads is not protected | pay2u operator patch `uap-policy-fix-APPLY.sh` exists; not upstreamed |
29
+ | 12 | Operator escape hatches are env-var-only (`UAP_DELIVER_BYPASS`, `UAP_WORKDIR_ALLOW`); env does not reliably reach hook processes in every harness, so the sanctioned override paths are effectively unreachable mid-session | `templates/hooks/uap-policy-gate.sh`, enforcers |
30
+ | 13 | Default executor model `qwen35-a3b` regardless of repo criticality | `src/cli/deliver.ts` defaults |
31
+ | 14 | Hook resolves the governing repo from the shell CWD / `cd` target, not from the operation's TARGET path — cross-repo writes are gated by one repo's policy but read the other repo's compliance state (observed: `uap plan validate` recorded in this repo was invisible to the gate anchored in pay2u) | `templates/hooks/uap-policy-gate.sh` MAIN_ROOT resolution; enforcers using relative `.uap/` state |
32
+
33
+ ---
34
+
35
+ ## Options per area
36
+
37
+ ### A. Kill the false-green (defects 1–3) — pick one, A2 recommended
38
+
39
+ - **A1 — No-op guard (minimum).** For coding missions, `success` requires a
40
+ non-empty applied-files set across the run (`filesApplied` union), unless the
41
+ caller passes `--allow-noop`. The `alreadyDelivered` baseline short-circuit is
42
+ only taken when the acceptance judge affirmatively confirms the mission is
43
+ already satisfied, with cited evidence. Small, surgical; does not fix gate
44
+ relevance (a run that writes one irrelevant file still "delivers").
45
+ - **A2 — Universal anti-vacuous floor (recommended).** Generalize the self-gate
46
+ rule that already exists for gate AUTHORING to the whole run: every deliver run
47
+ must have ≥1 required gate that FAILS at baseline. If all detected project
48
+ gates are green at turn 0, auto-engage self-gate authoring (today that happens
49
+ only when `noRealGates`). Change is essentially
50
+ `needsSelfGate = selfGateAllowed && (noRealGates || allRequiredGatesGreenAtBaseline || forceSelfGate)`
51
+ plus threading the baseline result into gate selection. HARD-FAIL rule: if the
52
+ required self-gate is still vacuous after its authoring retries (passes on the
53
+ unsolved repo), the RUN fails — today a vacuous gate is only a warning, which
54
+ re-opens the false-green door. This is the smallest change that makes
55
+ "delivered" always mean "something that was red is now green".
56
+ - **A3 — Mission-scoped gate relevance (deep).** Classify the mission → affected
57
+ paths → require gates whose declared scope covers those paths (needs B1's gate
58
+ manifests with `scope:` globs). Most correct; biggest lift; do after A2.
59
+
60
+ ### B. Polyglot gates (defect 6) — B1 recommended, B2 additive
61
+
62
+ - **B1 — Project-declared gates.** `.uap.json → delivery.gates[]`:
63
+ `{id, name, cmd, cwd, scope: [globs], required, tier, timeoutSec}`. Declared
64
+ gates merge with (and outrank) detected ones. pay2u would declare:
65
+ docker-buildx `--target builder` for `apps/api/**`, `gen_openapi.py --check`
66
+ for handler files, vitest workspace for `apps/web/**`. Also gives A3 its
67
+ scope metadata later.
68
+ - **B2 — Broader detection.** Recognize Dockerfile targets, CMakeLists, cargo,
69
+ pyproject, migrations dirs. Heuristic; useful defaults, never as trustworthy
70
+ as B1.
71
+
72
+ ### C. Verification rails (defects 4–5) — do both, small
73
+
74
+ - **C1 — Diff-aware acceptance.** The judge prompt always includes
75
+ `git diff --stat` + changed-file list since run start; hard rule before the
76
+ LLM call: empty diff ⇒ acceptance FAIL (for coding missions). An LLM judge
77
+ must never be the only thing standing between a no-op and success — make the
78
+ empty-diff case deterministic.
79
+ - **C2 — Decouple rails from `--no-auto`.** `--no-auto` disables exploration
80
+ aids only (candidates, ideation, practices, escalation). Acceptance judging
81
+ and the anti-vacuous floor are verification rails and stay on unless
82
+ explicitly disabled (`--no-acceptance`).
83
+
84
+ ### D. Autoroute with content (defects 7–8) — D1+D3 recommended, D2 policy choice
85
+
86
+ - **D1 — Capture the blocked edit as an intent.** The PreToolUse payload already
87
+ contains the exact `old_string`/`new_string`/`content`. The enforcer writes the
88
+ FULL intent into `.uap/pending-deliver.jsonl` (file, op, old, new, ts, agent).
89
+ The spawned deliver mission becomes "apply the recorded edit intents for
90
+ <file>" and the executor gets a deterministic `apply_pending_edits` fast path
91
+ (anchored exact-match application, no model improvisation; model only engages
92
+ when an anchor no longer matches). This converts "block and hope" into a
93
+ queue of exact diffs applied through the sanctioned pipeline with gates —
94
+ today's entire incident would have been a non-event.
95
+ - **D2 — Spawn policy.** Default OFF for auto-spawn (log + surface the pending
96
+ intent to the agent in the block message: "recorded; run `uap deliver
97
+ --pending <file>` to apply"), batch multiple intents per file per run. Avoids
98
+ background fan-out entirely; the agent stays in control of when to flush.
99
+ - **D3 — `edit_file` tool in the agentic executor.** Anchored `{path, old, new,
100
+ occurrence}` replacement alongside `write_file`. Needed by D1's fallback; also
101
+ removes the whole-file-rewrite hazard for large files generally.
102
+
103
+ ### E. Shared-workspace safety (defect 9)
104
+
105
+ - **E1 — Exclusive lease.** `deliver.lock` becomes a real mutex: a second run
106
+ against the same worktree refuses to start (override flag for operators).
107
+ Concurrent agents queue rather than interleave.
108
+ - **E2 — Scoped snapshots (recommended with E1).** keep-best rollback and
109
+ protect-tests restoration operate ONLY on files the run itself wrote (tracked
110
+ per-run), never tree-wide — a concurrent agent's test files can't be reverted
111
+ by someone else's deliver run.
112
+
113
+ ### F. Policy liveness (defects 10–12, 14) — the catch-22 class
114
+
115
+ - **F1 — Compliance-path liveness check (recommended, with a sabotage guard).**
116
+ Every enforcer declares its compliant path as machine-checkable requirements
117
+ (`requires:` — commands that must exist, dirs that must be agent-writable,
118
+ skills that must resolve). `uap policy enable`/install runs the liveness
119
+ check and re-runs it on session-start; a policy whose compliant path is dead
120
+ raises a loud operator alert and is marked "unhealthy" in `uap policy status`.
121
+ IMPORTANT — auto-degrade to advisory is NOT the default: an agent could
122
+ deliberately break a compliant path (delete a skill, wedge deliver) to force
123
+ a blocking policy down to advisory. Degrade-on-dead-path is opt-in per policy
124
+ (`degradeOnDeadPath: true`) and only when the breakage is outside
125
+ agent-writable surfaces; otherwise the policy stays blocking and the operator
126
+ decides. Liveness probe for deliver itself: a canary mission in a temp repo
127
+ that must produce a 1-line diff and pass its gate.
128
+ - **F2 — Upstream the self-protect inversion fix.** Port pay2u's operator patch
129
+ (`uap-policy-fix-APPLY.sh`): protect `agents/data/memory/policies.db` (the
130
+ enforcement DB) while allowing agent WRITES (not edits/deletes) to compliance
131
+ artifact paths the gates demand (`.uap/reviews/…`).
132
+ - **F3 — Operator overrides via signed file.** Honor
133
+ `.uap/operator-overrides.json` (owner-writable only; hook verifies uid/mode)
134
+ carrying `{deliverBypass: true, workdirAllow: [...], expiresAt}` in addition
135
+ to env vars — env doesn't reach hook subprocesses in all harnesses, which
136
+ today makes the documented escape hatches unreachable mid-session. Time-boxed
137
+ by `expiresAt` so overrides can't rot open.
138
+ - **F4 — Anchor policy state to the TARGET repo (defect 14).** The gate hook
139
+ resolves MAIN_ROOT from the operation's target path (file_path) when present,
140
+ falling back to cwd only for path-less ops; enforcers read state via
141
+ `UAP_STATE_DIR` derived from that root, never a bare relative `.uap/`.
142
+ Cross-repo work then meets the target repo's policies with the target repo's
143
+ compliance state.
144
+
145
+ ### G. Model routing (defect 13)
146
+
147
+ - **G1 —** `.uap.json → delivery.model` / `delivery.routing` as the
148
+ config-authoritative default (same authoritative-over-env pattern as
149
+ `delivery.enforcement`); ship a `criticality: money|normal|sandbox` shorthand
150
+ that maps to routing presets. Local qwen stays the default only for `sandbox`.
151
+
152
+ ---
153
+
154
+ ## Recommended phasing
155
+
156
+ - **P0 (stops the bleeding; small diffs):** A2 + C1 + C2, and D2's log-only
157
+ default for autoroute (one-line default flip). After P0, a false-green no-op
158
+ is structurally impossible and blind agents stop spawning.
159
+ - **P1 (makes the sanctioned path actually good):** D1 + D3 + B1 + E1 + E2.
160
+ After P1, a blocked edit flows through deliver as an exact diff with real,
161
+ project-declared gates — the enforcement stops costing precision.
162
+ - **P2 (kills the catch-22 class):** F1 + F2 + F3 + F4 + G1.
163
+ - **P3 (correctness ceiling):** A3 mission-scoped gate relevance on top of B1
164
+ scopes.
165
+
166
+ Rollout: fix in this repo → version-bump (1.145.0 for P0) → reinstall global
167
+ (`npm i -g …`) → pay2u picks it up next session; add regression tests:
168
+ (1) deliver on a green-gated repo with a real mission must NOT return
169
+ `alreadyDelivered` or ladder-pass without a failing-at-baseline required gate;
170
+ (2) empty-diff acceptance must fail; (3) autoroute intent must round-trip
171
+ `old/new` verbatim; (4) policy liveness canary on the two historical
172
+ catch-22s; (5) vacuous required self-gate ⇒ run failure, not warning;
173
+ (6) cross-repo write meets the TARGET repo's policy state.
@@ -0,0 +1,180 @@
1
+ # Deliver Hardening — Parallel-Review Response (2026-07-13)
2
+
3
+ Three review angles ran concurrently against the uncommitted hardening diff
4
+ (code-quality-reviewer, security-code-reviewer, architect-reviewer). This
5
+ document records every finding and its disposition. The implementation plan
6
+ itself is `deliver-hardening-plan-2026-07-13.md`.
7
+
8
+ ## Blockers — fixed
9
+
10
+ ### 1. E2 scoped restore was inert on the default executor (code-quality + architect)
11
+
12
+ `runFiles` came from `history[].filesApplied`, but the agentic executor (the
13
+ DEFAULT whenever the repo has inspectable content) writes through its own
14
+ tools behind a no-op applier, so the write-set was empty by construction: a
15
+ regressed run logged "nothing to do" and disposed the only good snapshot —
16
+ worse than the blast-radius defect E2 fixed.
17
+
18
+ **Fix:**
19
+
20
+ - `AgenticExecutorOptions.writeLedger` — a caller-owned per-run `Set` of
21
+ project-relative paths, recorded at the `write_file`/`edit_file`/`edit_range`
22
+ `writeFileSync` sites (NOT via the bash sweep's `authorised` map, which is a
23
+ no-op when bash is disabled — the common case).
24
+ - `SweepOutcome.changed` — the turn-end bash sweep now reports every
25
+ stat-moved path, judged or not, so shell writes reach the same ledger.
26
+ - deliver.ts drains the ledger in the applier seam, so
27
+ `history[].filesApplied` is truthful again. The isolated-task executor is
28
+ deliberately NOT wired in: its paths are relative to its own tree and would
29
+ mis-target the projectRoot-scoped restore.
30
+ - Empty write-set + warranted rollback now fails LOUD: the snapshot is
31
+ preserved with an operator-actionable message instead of being silently
32
+ disposed. (A whole-tree fallback restore was considered and rejected — in a
33
+ shared worktree it reverts other agents' work, the incident E2 exists to
34
+ fix.)
35
+ - The convergence loop's idle-breaker comment was updated: `filesApplied` is
36
+ no longer empty-by-construction on the agentic path.
37
+
38
+ Tests: `test/delivery/agentic-write-ledger.test.ts` (8: tool writes recorded,
39
+ refusals not, end-to-end through a mocked model, shell write via the sweep),
40
+ plus a `changed`-field test in `bash-sweep.test.ts`.
41
+
42
+ ### 2. operator-overrides.json was agent-mintable (security BLOCK)
43
+
44
+ Every check on the file was satisfiable by a same-uid agent (uid==euid is
45
+ vacuous when the agent IS the user), and an interpreter-mediated write
46
+ (`python3 -c 'open(...)'`) walked past the self-protect text scan — one Bash
47
+ call minted `deliverBypass` + `selfProtectOff`, neutralizing all enforcement.
48
+
49
+ **Fix:**
50
+
51
+ - The hook now requires the override file (and the liveness cache, below) to
52
+ be **ROOT-owned** (`st_uid == 0`), regular, not group/world-writable,
53
+ symlink-free (O_NOFOLLOW + fstat-on-fd), and unexpired. Root ownership is
54
+ the only check with teeth under a same-uid agent: the operator plants the
55
+ file with `sudo`, the agent cannot. (When the agent itself runs as root, no
56
+ local check can help — enforcement is already theater there.)
57
+ - Defense-in-depth: `enforcement_self_protect.py` gained an interpreter-write
58
+ rule — a bash command naming either trust anchor
59
+ (`.uap/operator-overrides.json`, `.uap/policy-liveness.json`) with an
60
+ interpreter (python/perl/ruby/node) and a write primitive
61
+ (`open(…"w"/"a"/"x"/"+")`, `.write*/.dump/.rename/.unlink/…`) is blocked at
62
+ any wrapper depth. Reads (`open` without a mode, `json.load`, `cat`) stay
63
+ allowed; ordinary `.uap` runtime state stays writable.
64
+ - Newline/colon injection through `workdirAllow` values is rejected (a value
65
+ carrying `\nUAP_SELF_PROTECT_OFF=1` would have smuggled an extra export into
66
+ the hook's eval loop).
67
+
68
+ Tests: the override suite was restructured around the new trust model — the
69
+ user-owned-refusal case runs everywhere (it IS the security property); the
70
+ trusted-file cases are `runIf(getuid()===0)` since a non-root process cannot
71
+ mint a root-owned fixture by construction. New injection test. Six new
72
+ enforcer tests (`test_enforcement_self_protect.py`).
73
+
74
+ ## High — fixed
75
+
76
+ ### 3. Liveness cache trusted blindly (security)
77
+
78
+ The degrade consult now applies the same trust terms as the override file:
79
+ root-owned, regular, not group/world-writable, no symlink, and FRESH
80
+ (`checkedAt` < 24h — a fixed path silently re-tightens; staleness bound was
81
+ the architect's should-fix). The file was already added to PROTECTED_TARGETS
82
+ pre-review. Consequence, documented in the hook and tests: `uap policy
83
+ liveness` run as the user writes the cache for STATUS display; the gate never
84
+ degrades on it. No seeded policy sets `degradeOnDeadPath` today, so no live
85
+ behavior changes; when one does, the operator refreshes the cache elevated —
86
+ a deliberate act, matching the override story.
87
+
88
+ ### 4. Declared-gates cwd containment (security #3, partial)
89
+
90
+ `runRung` refuses a declared `cwd` that resolves outside the project root
91
+ (one-line containment check, fail-closed with a clear message; test added).
92
+ The deeper finding — `.uap.json` is writable via interpreter-mediated bash,
93
+ and declared gates make that RCE — is PRE-EXISTING and honestly documented in
94
+ the enforcer (`HONEST SCOPE`); the same interpreter-write class now blocked
95
+ for the trust anchors could be extended to `.uap.json` in a follow-up. The
96
+ id-shadowing of detected rungs by declared ones is an intended feature
97
+ (documented outrank semantics), kept.
98
+
99
+ ## Should-fix — fixed
100
+
101
+ - **Liveness worktree alignment (code-quality + architect):** `liveness.ts`
102
+ gained `mainCheckoutRoot()` (git toplevel + `.worktrees` strip, same rule
103
+ as the hook's `${CHECKOUT_ROOT%%/.worktrees/*}`), applied in
104
+ `checkPolicyLiveness`/`writeLivenessCache`/`readLivenessCache`/
105
+ `enabledPolicySlugs`. Session-start runs the refresh from `COORD_ROOT`.
106
+ Previously a worktree session (the mandated workflow) wrote the cache and
107
+ read the DB where the gate never looks — F1 was inert there.
108
+ - **Cache contract:** `version: 1` field added; the slug rule is documented
109
+ as one rule in three places (TS writer, `policyNameSlug`, hook re.sub).
110
+ - **`classifySurface`** now compares separator-terminated (`/repo-evil` no
111
+ longer classifies as inside `/repo`), and an unwritable dir INSIDE the repo
112
+ is `agent-writable` (agent could chmod it — the hardcoded `external` was a
113
+ sabotage hole once any policy opts into degrade).
114
+ - **Python execution gate** prefers `.venv/bin/python` over ambient
115
+ `python3` (deps live in the venv; ambient import failed healthy repos).
116
+ Test with a stub venv interpreter added.
117
+ - **`runNativeBin`** no longer counts a binary that failed to exec at all
118
+ (ENOENT/bad shebang/wrong arch) as pass evidence; all-unexecutable reads as
119
+ an honest skip-pass ("not measurable here"), not a pass.
120
+ - **Hook hot path:** the override parse is guarded by an existence early-out
121
+ — zero python spawns on the common path. (Merging the two payload parses
122
+ was considered and skipped: churn on a string the tests pin byte-exactly,
123
+ for ~one spawn on path-carrying ops only.)
124
+ - **F4 anchor:** walks to the nearest EXISTING ancestor directory (a Write
125
+ into a not-yet-created dir in another repo no longer falls back to cwd),
126
+ and the extractor accepts the same key set as the self-protect extractor
127
+ (`file_path`, `path`, `filePath`, `target`, `filename`, `file`).
128
+ - **deliver.ts** uses `Object.hasOwn` for the criticality lookup (prototype-
129
+ chain values like `"constructor"` no longer pass).
130
+ - **plan.ts** `stateDir` honors an ABSOLUTE `UAP_STATE_DIR` (the hook exports
131
+ it as `$MAIN_ROOT/.uap` since F4; `join` would have concatenated it).
132
+ - **acceptance-judge** labels the git section "UNCOMMITTED CHANGES IN THIS
133
+ TREE" with a shared-worktree caveat instead of "CHANGES IN THIS RUN" — the
134
+ diff is against HEAD, so in a shared worktree it lists other agents' work
135
+ and the old label actively misled the judge.
136
+ - **acceptance-judge test fixture** sets local git identity (flakes on
137
+ machines without global user.email).
138
+ - **`uap policy status` help** documents the cache-write side effect.
139
+
140
+ ## Considered, not done
141
+
142
+ - **Whole-tree restore fallback for an empty write-set** — rejected: it is
143
+ the shared-worktree incident E2 fixes. Loud preserve instead.
144
+ - **Signing overrides with an HMAC/keyfile instead of root ownership** —
145
+ root ownership is simpler, needs no key management, and is auditable with
146
+ `ls -l`. Revisit if multi-user operator workflows appear.
147
+ - **Re-deriving `degradable` in the hook from the registry** — the registry
148
+ is TS; the hook is bash. Root-ownership + freshness + the opt-in bit in the
149
+ cache achieves the same guard without a TS eval in the hot path.
150
+ - **ADR adoption** — the repo convention is `docs/plans/`; the plan doc plus
151
+ this response satisfy the decision-record intent.
152
+
153
+ ## Behavior changes operators should know (in the commit message)
154
+
155
+ 1. **`UAP_STATE_DIR` is now exported by the gate hook as `$MAIN_ROOT/.uap`.**
156
+ In-flight per-worktree state (`plan_state.json`, `read_log.state`, IaC
157
+ acks) is orphaned once — a one-time fail-closed reset: sessions may be
158
+ re-prompted for plan review. State then lives consistently in the main
159
+ checkout instead of fragmenting per worktree.
160
+ 2. **Config beats env for deliver model routing** (G1): a declared
161
+ `delivery.model`/`routing`/`criticality` in `.uap.json` now outranks
162
+ ambient `UAP_DELIVER_MODEL`/`UAP_DELIVER_ROUTING`.
163
+ 3. **The operator-override file must be root-owned** (sudo) to be honored —
164
+ the original uid/mode-only check was vacuous against a same-uid agent.
165
+ 4. **The liveness degrade path** consults only root-owned, fresh (<24h)
166
+ caches.
167
+
168
+ ## Verification
169
+
170
+ - `tsc --noEmit` clean.
171
+ - All touched vitest suites green (ledger 8, bash-sweep 39, snapshot-scoped
172
+ 10, declared-gates 9, execution-gate-polyglot 13, acceptance-judge 26,
173
+ policy-liveness 12, gate hook suites 22, routing 13, run-state 15).
174
+ - Python enforcer suite: 1212 tests; 3F+9E all in `test_uap_compliance`
175
+ (live-DB schema checks), byte-identical failure set on unmodified master —
176
+ pre-existing environmental.
177
+ - Hook mirror parity enforced by `test_gate_failclosed_indirection` +
178
+ `test_gate_failclosed_schema_diff` (these caught an unsynced mirror during
179
+ remediation — the parity net works).
180
+ - Full vitest suite: see PR body for the final run.
@@ -0,0 +1,216 @@
1
+ # Kimi K3 × UAP Uplift Validation — Factory Droid Harness Plan
2
+
3
+ **Status:** Proposed (awaiting go-ahead before any paid API runs)
4
+ **Date:** 2026-09-02
5
+ **Supersedes:** the earlier opencode-based variant of this plan (the harness is now Factory Droid, the harness this repo's own sessions run in)
6
+
7
+ ## Goal
8
+
9
+ Measure UAP's uplift on a frontier open-weight model by re-running an existing,
10
+ publicly-anchored benchmark with UAP enabled vs disabled, holding **model,
11
+ harness, task set, and attempt counts fixed**. The model is **Kimi K3**
12
+ (Moonshot AI, 2.8T-param MoE, open-weight, July 2026); the harness is
13
+ **Factory Droid** in headless mode (`droid exec`); the benchmark is
14
+ **Terminal-Bench 2.1**.
15
+
16
+ The only thing that differs between the two arms is the UAP treatment. That
17
+ paired design is the entire experiment: any resolution-rate delta is
18
+ attributable to UAP and nothing else.
19
+
20
+ ## Why Terminal-Bench 2.1
21
+
22
+ K3's published scores with harness attribution (from the MoonshotAI/Kimi-K3
23
+ tech report and repo):
24
+
25
+ | Benchmark | K3 score | Harness | Re-runnable? |
26
+ |---|---|---|---|
27
+ | **Terminal-Bench 2.1** | **88.3** | Kimi Code | Yes — public tasks, deterministic verifiers, API-feasible |
28
+ | SWE-bench Verified | 93.4 | Vals AI | Partially — harness not public |
29
+ | SWE-Marathon | 42.0 | Claude Code / Harbor | Yes, but long-horizon = expensive |
30
+ | PostTrainBench | 36.6 | Claude Code | Yes, but GPU-calibrated |
31
+ | DeepSWE / FrontierSWE / Kimi-Code-Bench | various | internal | No — harness-locked |
32
+
33
+ Terminal-Bench 2.1 (89 Dockerized terminal tasks, time-boxed, resolved only
34
+ when post-run verifier tests pass) is the cleanest anchor:
35
+
36
+ - Public dataset with deterministic, non-LLM verifiers — no judge drift.
37
+ - Already supported by our in-repo paired-harness pattern
38
+ (`benchmarks/terminal_bench/uap_opencode_agent.py`), which we adapt to Droid.
39
+ - Factory has published its own Droid-on-Terminal-Bench methodology
40
+ (factory.ai/news/terminal-bench, Sept 2025, TB Core v0.1.1), so our Droid
41
+ setup mirrors a canonical, publicly-described configuration rather than an
42
+ ad-hoc one.
43
+
44
+ ### Comparability caveat (read before quoting numbers)
45
+
46
+ K3's 88.3 was achieved on the **Kimi Code** harness, not Droid. Factory's own
47
+ TB analysis found harness design moves scores as much as model choice (Droid +
48
+ Sonnet beat Claude Code + Opus). So:
49
+
50
+ - **Primary claim (internally valid):** paired Δ = UAP-on minus UAP-off, same
51
+ Droid+K3 stack. This is the number the experiment is designed to produce.
52
+ - **Secondary claim (cross-harness, caveated):** Droid+K3+UAP vs the published
53
+ 88.3 Kimi-Code anchor. Report it, always with the harness difference named.
54
+
55
+ ## Configuration
56
+
57
+ ### Kimi K3 via BYOK (`~/.factory/settings.json` inside the task container)
58
+
59
+ Primary endpoint — Moonshot's official API (OpenAI-compatible dialect):
60
+
61
+ ```json
62
+ {
63
+ "customModels": [
64
+ {
65
+ "model": "kimi-k3",
66
+ "displayName": "Kimi K3",
67
+ "baseUrl": "https://api.platform.kimi.ai/v1",
68
+ "apiKey": "${KIMI_API_KEY}",
69
+ "provider": "generic-chat-completion-api",
70
+ "maxOutputTokens": 65536,
71
+ "extraArgs": { "temperature": 1.0, "top_p": 1.0 }
72
+ }
73
+ ]
74
+ }
75
+ ```
76
+
77
+ Fallback endpoint — OpenRouter (`baseUrl: https://openrouter.ai/api/v1`,
78
+ `model: moonshotai/kimi-k3`, `apiKey: ${OPENROUTER_API_KEY}`). Identical
79
+ pricing at time of writing: **$2.55 / Mtok input, $12.75 / Mtok output**.
80
+
81
+ Sampling parameters follow the K3 tech report's agentic-eval settings:
82
+ `temperature=1.0`, `top_p=1.0`, maximum reasoning effort. Droid's
83
+ `-r/--reasoning-effort` flag does **not** apply to custom models; if the
84
+ endpoint exposes a reasoning-effort knob (e.g. `reasoning_effort`), pass it
85
+ through `extraArgs` and confirm acceptance during Phase 0.
86
+
87
+ Model reference on the command line: `custom:Kimi-K3-0` (display name with
88
+ spaces dasherized + 0-based index into `customModels`).
89
+
90
+ ### Arms
91
+
92
+ | Arm | Model route | UAP treatment |
93
+ |---|---|---|
94
+ | **A — Baseline** | Droid → K3 direct | none |
95
+ | **B — UAP (protocol)** | Droid → K3 direct | UAP operating protocol dropped as `AGENTS.md` in the task workdir (Droid reads `AGENTS.md` natively) — same honest treatment surface as the existing opencode paired shim: gates discipline, verify-don't-assume, stop-when-green |
96
+ | **C — UAP (enforced, optional)** | Droid → **UAP proxy :4100** → K3 | Arm B **plus** the UAP proxy in the request path, so deterministic enforcers (MANDATE-DELIVER, guardrails) are in the loop. Mirrors the `UAP_TB_UAP_BASE_URL` pattern of the opencode shim. |
97
+
98
+ A vs B is the primary paired comparison (cheapest, cleanest attribution).
99
+ C runs only if A/B shows a signal worth deepening, since it exercises the full
100
+ enforcement stack rather than prompt-level discipline alone.
101
+
102
+ ### Droid invocation (per TB task, inside the task container)
103
+
104
+ Mirrors Factory's published TB methodology: non-interactive task mode, all
105
+ permissions skipped (containers are disposable), task specification passed as
106
+ the prompt.
107
+
108
+ ```bash
109
+ droid exec \
110
+ --skip-permissions-unsafe \
111
+ --model "custom:Kimi-K3-0" \
112
+ --output-format json \
113
+ "$(cat /task/instruction.md)"
114
+ ```
115
+
116
+ The UAP arm additionally writes `AGENTS.md` into the task working directory
117
+ before the run (and, for arm C, points the model `baseUrl` at the proxy).
118
+
119
+ ### New adapter: `benchmarks/terminal_bench/uap_droid_agent.py`
120
+
121
+ A custom `AbstractInstalledAgent` — the same integration point Factory used
122
+ for its own TB runs — with two exported classes, `DroidBaseline` and
123
+ `DroidUAP`, differing only in the AGENTS.md drop (and proxy base URL for arm
124
+ C). A companion `uap-droid-setup.sh.j2` runs inside the container:
125
+
126
+ 1. Install the Droid CLI: `curl -fsSL https://app.factory.ai/cli | sh`
127
+ 2. Write `~/.factory/settings.json` with the customModels block above
128
+ 3. Export `FACTORY_API_KEY` (headless auth) and the K3 provider key from
129
+ host-passed env (`UAP_TB_KIMI_API_KEY` / `UAP_TB_OPENROUTER_API_KEY`,
130
+ `UAP_TB_FACTORY_API_KEY`)
131
+
132
+ Run shape (from the bench venv):
133
+
134
+ ```bash
135
+ UAP_TB_FACTORY_API_KEY=fk-... UAP_TB_KIMI_API_KEY=sk-... \
136
+ .tbvenv/bin/tb run \
137
+ --agent-import-path uap_droid_agent:DroidBaseline \
138
+ --dataset terminal-bench==2.1 -t <task-id> --n-attempts 5
139
+ # then again with :DroidUAP, and diff the two results.json
140
+ ```
141
+
142
+ (Exact TB 2.1 dataset/registry name to be confirmed during Phase 0; the
143
+ harness moved to the Harbor registry since the opencode shim was written.)
144
+
145
+ ## Protocol
146
+
147
+ Three phases, each gated on the previous one. **No paid run starts without an
148
+ explicit go from the user.**
149
+
150
+ ### Phase 0 — Smoke (no benchmark scoring; ~$5)
151
+
152
+ Single cheap TB task (e.g. a hello-world-tier task), a few attempts, both
153
+ arms. Validates:
154
+
155
+ 1. K3 responds correctly through Droid's `generic-chat-completion-api`
156
+ provider — tool calls parse, multi-turn works.
157
+ 2. **Preserved thinking history:** K3 requires prior `reasoning_content` to be
158
+ passed back verbatim across turns. Whether Droid round-trips this field for
159
+ generic providers is unknown — inspect request logs (or the arm-C proxy
160
+ log) to confirm. If it does not round-trip, note the degradation, keep both
161
+ arms identical (the paired comparison remains valid), and consider the
162
+ Anthropic-compatible Kimi endpoint with `provider: "anthropic"` as a
163
+ variant.
164
+ 3. Container networking: container → provider egress; container → host proxy
165
+ for arm C.
166
+ 4. Headless auth: `FACTORY_API_KEY` works non-interactively in the container.
167
+
168
+ ### Phase 1 — Pilot (~10–12 tasks × 3 attempts × 2 arms; est. $40–90)
169
+
170
+ Stratified sample across TB 2.1 categories (coding, build/deps, data/ML,
171
+ systems, security). Purpose: shake out adapter bugs, measure per-run token
172
+ burn to firm up the Phase 2 budget, and check variance. No conclusions drawn.
173
+
174
+ ### Phase 2 — Full sweep (89 tasks × 5 attempts × 2 arms)
175
+
176
+ Five attempts per task matches both Factory's published TB convention and the
177
+ existing paired-harness format. Seeds fixed where the endpoint allows;
178
+ temperature/top_p held at the tech-report values in both arms.
179
+
180
+ ### Analysis (format per `docs/benchmarks/PAIRED_FINDINGS.md`)
181
+
182
+ - Per-task paired resolution rates; Δ with bootstrap 95% CI.
183
+ - McNemar 2×2 on discordant task-attempt pairs.
184
+ - Secondary: tokens/cost per resolved task, wall-clock, gate-loop behavior
185
+ (arm B should show more build/test iterations and fewer one-shot stops).
186
+ - Report both the paired Δ (primary) and the Droid+K3+UAP absolute score vs
187
+ the 88.3 Kimi-Code anchor (secondary, harness caveat attached).
188
+
189
+ ## Cost estimate
190
+
191
+ Rough per-run burn for TB-scale tasks under Droid (environment bootstrap +
192
+ multi-turn tool loop): assume 0.3–1.5M input tokens and 20–80k output tokens
193
+ per attempt. At $2.55/$12.75 per Mtok that is ~**$1.00–4.85 per attempt**.
194
+ Full sweep = 89 × 5 × 2 = 890 attempts → **~$900–4,300**. Pilot firms this up
195
+ before any full-sweep commitment. Prompt caching, if the endpoint honors it
196
+ for generic providers, cuts the input side substantially.
197
+
198
+ ## Risks and open questions
199
+
200
+ | Risk | Impact | Mitigation |
201
+ |---|---|---|
202
+ | `reasoning_content` not round-tripped by Droid's generic provider | K3 degrades below published behavior | Phase 0 log inspection; both arms affected equally (paired Δ still valid); try Anthropic-compatible endpoint variant |
203
+ | Custom models get Droid's generic tool scaffolding, not per-model tuning | Absolute score likely below Kimi Code's 88.3 even before UAP | Expected and acceptable — the paired Δ is the claim; report absolute with caveat |
204
+ | Headless `FACTORY_API_KEY` auth inside containers | Adapter blocked | Phase 0 smoke |
205
+ | K3 rate limits / TTFT under parallel attempts | Wall-clock blowup on TB's aggressive timeouts | Bounded concurrency (xargs -P), retry-with-backoff in the adapter |
206
+ | Exact TB 2.1 registry/dataset name changed under Harbor | Run command fails | Confirm in Phase 0 against the current terminal-bench docs |
207
+ | Endpoint pricing moves | Budget | Re-quote at Phase 2 kickoff |
208
+
209
+ ## Deliverables when executed
210
+
211
+ 1. `benchmarks/terminal_bench/uap_droid_agent.py` + `uap-droid-setup.sh.j2`
212
+ (the adapter; not part of this plan PR)
213
+ 2. Phase 0/1/2 run artifacts under `benchmark-results/` in the established
214
+ paired format
215
+ 3. A findings doc modeled on `docs/benchmarks/PAIRED_FINDINGS.md` with the
216
+ paired Δ, CI, McNemar table, and the caveated cross-harness comparison