@kontourai/flow-agents 3.3.0 → 3.4.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.
Files changed (256) hide show
  1. package/.github/workflows/add-to-project.yml +15 -0
  2. package/.github/workflows/ci.yml +161 -0
  3. package/CHANGELOG.md +41 -0
  4. package/CONTEXT.md +5 -1
  5. package/README.md +19 -8
  6. package/build/src/builder-flow-run-adapter.d.ts +80 -0
  7. package/build/src/builder-flow-run-adapter.js +241 -0
  8. package/build/src/builder-flow-runtime.d.ts +16 -0
  9. package/build/src/builder-flow-runtime.js +290 -0
  10. package/build/src/cli/builder-run.d.ts +1 -0
  11. package/build/src/cli/builder-run.js +27 -0
  12. package/build/src/cli/effective-backlog-settings.js +70 -2
  13. package/build/src/cli/init.d.ts +34 -0
  14. package/build/src/cli/init.js +341 -61
  15. package/build/src/cli/kit.js +55 -12
  16. package/build/src/cli/pull-work-provider.js +346 -5
  17. package/build/src/cli/skill-drift-check.d.ts +1 -0
  18. package/build/src/cli/skill-drift-check.js +165 -0
  19. package/build/src/cli/telemetry-doctor.d.ts +37 -0
  20. package/build/src/cli/telemetry-doctor.js +53 -6
  21. package/build/src/cli/validate-hook-influence.js +37 -7
  22. package/build/src/cli/workflow-sidecar.d.ts +93 -8
  23. package/build/src/cli/workflow-sidecar.js +1175 -158
  24. package/build/src/cli.js +5 -0
  25. package/build/src/flow-kit/validate.d.ts +54 -34
  26. package/build/src/flow-kit/validate.js +237 -26
  27. package/build/src/index.d.ts +2 -0
  28. package/build/src/index.js +1 -0
  29. package/build/src/lib/console-connect-options.d.ts +97 -0
  30. package/build/src/lib/console-connect-options.js +199 -0
  31. package/build/src/lib/console-telemetry-validate.d.ts +49 -0
  32. package/build/src/lib/console-telemetry-validate.js +91 -0
  33. package/build/src/lib/flow-resolver.d.ts +54 -1
  34. package/build/src/lib/flow-resolver.js +112 -5
  35. package/build/src/lib/fs.d.ts +17 -0
  36. package/build/src/lib/fs.js +172 -0
  37. package/build/src/lib/local-artifact-root.d.ts +44 -1
  38. package/build/src/lib/local-artifact-root.js +131 -3
  39. package/build/src/runtime-adapters.d.ts +39 -3
  40. package/build/src/runtime-adapters.js +77 -31
  41. package/build/src/tools/build-universal-bundles.js +40 -2
  42. package/build/src/tools/codex-agent-routing.d.ts +2 -0
  43. package/build/src/tools/codex-agent-routing.js +49 -0
  44. package/build/src/tools/generate-context-map.js +1 -0
  45. package/build/src/tools/validate-source-tree.js +27 -1
  46. package/context/scripts/hooks/lib/kit-catalog.js +235 -0
  47. package/context/scripts/hooks/lib/runnable-command.js +177 -0
  48. package/context/scripts/hooks/stop-goal-fit.js +278 -48
  49. package/context/scripts/hooks/workflow-steering.js +121 -21
  50. package/context/scripts/package.json +3 -0
  51. package/context/scripts/telemetry/install-console-config.sh +25 -4
  52. package/context/scripts/telemetry/lib/config.sh +102 -12
  53. package/context/scripts/telemetry/lib/pricing.sh +50 -0
  54. package/context/scripts/telemetry/lib/session.sh +3 -0
  55. package/context/scripts/telemetry/lib/transport.sh +87 -0
  56. package/context/scripts/telemetry/lib/usage.sh +205 -4
  57. package/context/scripts/telemetry/telemetry.conf +6 -0
  58. package/context/scripts/telemetry/telemetry.sh +48 -0
  59. package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
  60. package/docs/agent-usage-feedback-loop.md +35 -0
  61. package/docs/architecture-engine-and-kits.md +110 -0
  62. package/docs/context-map.md +2 -0
  63. package/docs/decisions/embeddable-engine.md +152 -0
  64. package/docs/decisions/index.md +3 -1
  65. package/docs/decisions/trust-ledger-retention.md +88 -0
  66. package/docs/decisions/workflow-enforcement.md +31 -9
  67. package/docs/fixture-ownership.md +3 -0
  68. package/docs/implementing-trust-reconciliation.md +129 -0
  69. package/docs/index.md +19 -9
  70. package/docs/integrations/flow-agents-console.md +167 -0
  71. package/docs/kit-authoring-guide.md +52 -21
  72. package/docs/spec/builder-flow-runtime.md +80 -0
  73. package/docs/spec/runtime-hook-surface.md +45 -1
  74. package/docs/specs/economics-record-contract.md +270 -0
  75. package/docs/specs/harness-capability-matrix.md +74 -0
  76. package/docs/specs/learning-review-proposals-contract.md +340 -0
  77. package/docs/specs/routing-efficiency-review.md +59 -0
  78. package/docs/verifiable-trust.md +74 -25
  79. package/docs/workflow-usage-guide.md +10 -0
  80. package/evals/acceptance/prove-capture-teeth.sh +132 -0
  81. package/evals/ci/antigaming-suite.sh +1 -0
  82. package/evals/ci/run-baseline.sh +72 -4
  83. package/evals/fixtures/economics/acceptance.json +12 -0
  84. package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
  85. package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
  86. package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
  87. package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
  88. package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
  89. package/evals/fixtures/economics/critique.json +22 -0
  90. package/evals/fixtures/economics/expected-record.json +71 -0
  91. package/evals/fixtures/economics/session-usage-event.json +1 -0
  92. package/evals/fixtures/economics/state.json +11 -0
  93. package/evals/fixtures/economics/transcript.jsonl +3 -0
  94. package/evals/fixtures/hook-influence/cases.json +7 -7
  95. package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
  96. package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
  97. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
  98. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
  99. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
  100. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
  101. package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
  102. package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
  103. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
  104. package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
  105. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
  106. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
  107. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
  108. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
  109. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
  110. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
  111. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
  112. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
  113. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
  114. package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
  115. package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
  116. package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
  117. package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
  118. package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
  119. package/evals/integration/test_builder_entry_enforcement.sh +241 -0
  120. package/evals/integration/test_builder_step_producers.sh +18 -10
  121. package/evals/integration/test_bundle_install.sh +172 -0
  122. package/evals/integration/test_console_tenant_isolation.sh +167 -0
  123. package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
  124. package/evals/integration/test_dual_emit_flow_step.sh +10 -4
  125. package/evals/integration/test_economics_record.sh +674 -0
  126. package/evals/integration/test_effective_backlog_settings.sh +1 -1
  127. package/evals/integration/test_evidence_capture_hook.sh +17 -2
  128. package/evals/integration/test_exemption_usage_review.sh +198 -0
  129. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  130. package/evals/integration/test_flow_kit_install_git.sh +83 -0
  131. package/evals/integration/test_flowdef_session_activation.sh +0 -1
  132. package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
  133. package/evals/integration/test_gate_lockdown.sh +7 -0
  134. package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
  135. package/evals/integration/test_goal_fit_hook.sh +2031 -0
  136. package/evals/integration/test_hook_category_behaviors.sh +8 -1
  137. package/evals/integration/test_hook_influence_cases.sh +25 -1
  138. package/evals/integration/test_install_merge.sh +227 -2
  139. package/evals/integration/test_kit_conformance_levels.sh +6 -6
  140. package/evals/integration/test_learning_review_proposals.sh +329 -0
  141. package/evals/integration/test_liveness_conflict_injection.sh +26 -22
  142. package/evals/integration/test_liveness_console_relay.sh +166 -0
  143. package/evals/integration/test_liveness_heartbeat.sh +17 -17
  144. package/evals/integration/test_liveness_worktree_root.sh +575 -0
  145. package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
  146. package/evals/integration/test_publish_delivery.sh +331 -1
  147. package/evals/integration/test_pull_work_board.sh +200 -0
  148. package/evals/integration/test_pull_work_provider.sh +1 -1
  149. package/evals/integration/test_record_check.sh +378 -0
  150. package/evals/integration/test_routing_efficiency.sh +71 -0
  151. package/evals/integration/test_runtime_adapter_activation.sh +28 -0
  152. package/evals/integration/test_session_resume_roundtrip.sh +16 -19
  153. package/evals/integration/test_skill_drift_check.sh +870 -0
  154. package/evals/integration/test_telemetry.sh +445 -0
  155. package/evals/integration/test_telemetry_doctor.sh +66 -0
  156. package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
  157. package/evals/integration/test_trust_reconcile_negatives.sh +30 -13
  158. package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
  159. package/evals/integration/test_usage_cost.sh +61 -0
  160. package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
  161. package/evals/integration/test_workflow_steering_hook.sh +157 -16
  162. package/evals/integration/test_workspace_settings.sh +176 -0
  163. package/evals/lib/env.sh +26 -0
  164. package/evals/lib/node.sh +8 -0
  165. package/evals/run.sh +29 -0
  166. package/evals/static/test_ci_integration_coverage.sh +115 -0
  167. package/evals/static/test_declared_scope_forms_documented.sh +114 -0
  168. package/evals/static/test_universal_bundles.sh +34 -0
  169. package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
  170. package/evals/static/test_workflow_skills.sh +1 -1
  171. package/kits/builder/flows/build.flow.json +9 -18
  172. package/kits/builder/flows/publish-learn.flow.json +5 -1
  173. package/kits/builder/kit.json +120 -0
  174. package/kits/builder/skills/deliver/SKILL.md +42 -0
  175. package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
  176. package/kits/builder/skills/execute-plan/SKILL.md +9 -0
  177. package/kits/builder/skills/learning-review/SKILL.md +51 -0
  178. package/kits/builder/skills/plan-work/SKILL.md +17 -20
  179. package/kits/builder/skills/pull-work/SKILL.md +21 -0
  180. package/kits/builder/skills/release-readiness/SKILL.md +12 -0
  181. package/kits/knowledge/kit.json +9 -0
  182. package/kits/veritas-governance/docs/README.md +35 -7
  183. package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
  184. package/kits/veritas-governance/kit.json +14 -0
  185. package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
  186. package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
  187. package/package.json +2 -2
  188. package/packaging/manifest.json +29 -0
  189. package/schemas/backlog-provider-settings.schema.json +13 -0
  190. package/schemas/workflow-state.schema.json +44 -0
  191. package/scripts/README.md +4 -0
  192. package/scripts/check-content-boundary.cjs +8 -1
  193. package/scripts/ci/trust-reconcile.js +136 -0
  194. package/scripts/hooks/codex-hook-adapter.js +77 -2
  195. package/scripts/hooks/evidence-capture.js +38 -5
  196. package/scripts/hooks/lib/codex-exit-code.js +316 -0
  197. package/scripts/hooks/lib/kit-catalog.js +235 -0
  198. package/scripts/hooks/lib/liveness-write.js +28 -1
  199. package/scripts/hooks/lib/local-artifact-paths.js +97 -1
  200. package/scripts/hooks/lib/runnable-command.js +177 -0
  201. package/scripts/hooks/lib/skill-drift.js +350 -0
  202. package/scripts/hooks/stop-goal-fit.js +278 -48
  203. package/scripts/hooks/workflow-steering.js +121 -21
  204. package/scripts/install-codex-home.sh +97 -47
  205. package/scripts/install-merge.js +72 -14
  206. package/scripts/install-owned-files.js +178 -0
  207. package/scripts/liveness/relay.sh +84 -0
  208. package/scripts/telemetry/economics-record.schema.json +145 -0
  209. package/scripts/telemetry/economics-record.sh +331 -0
  210. package/scripts/telemetry/install-console-config.sh +25 -4
  211. package/scripts/telemetry/learning-review-decide.sh +124 -0
  212. package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
  213. package/scripts/telemetry/learning-review-proposals.sh +484 -0
  214. package/scripts/telemetry/lib/config.sh +102 -12
  215. package/scripts/telemetry/lib/pricing.sh +14 -6
  216. package/scripts/telemetry/lib/session.sh +3 -0
  217. package/scripts/telemetry/lib/transport.sh +133 -15
  218. package/scripts/telemetry/lib/usage.sh +121 -28
  219. package/scripts/telemetry/routing-efficiency.sh +0 -0
  220. package/scripts/telemetry/telemetry.conf +6 -0
  221. package/scripts/telemetry/telemetry.sh +48 -0
  222. package/src/builder-flow-run-adapter.ts +357 -0
  223. package/src/builder-flow-runtime.ts +348 -0
  224. package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
  225. package/src/cli/builder-flow-runtime.test.mjs +213 -0
  226. package/src/cli/builder-run.ts +28 -0
  227. package/src/cli/codex-agent-routing.test.mjs +44 -0
  228. package/src/cli/codex-exit-code.test.mjs +207 -0
  229. package/src/cli/console-connect-options.test.mjs +329 -0
  230. package/src/cli/console-telemetry-validate.test.mjs +157 -0
  231. package/src/cli/effective-backlog-settings.ts +68 -2
  232. package/src/cli/flow-resolver-composition.test.mjs +72 -0
  233. package/src/cli/init.test.mjs +161 -0
  234. package/src/cli/init.ts +407 -62
  235. package/src/cli/kit-metadata-security.test.mjs +443 -0
  236. package/src/cli/kit.ts +50 -12
  237. package/src/cli/pull-work-provider.ts +377 -3
  238. package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
  239. package/src/cli/skill-drift-check.ts +196 -0
  240. package/src/cli/telemetry-doctor.test.mjs +53 -0
  241. package/src/cli/telemetry-doctor.ts +50 -7
  242. package/src/cli/validate-hook-influence.ts +37 -6
  243. package/src/cli/workflow-sidecar.ts +1150 -151
  244. package/src/cli.ts +5 -0
  245. package/src/flow-kit/validate.ts +277 -38
  246. package/src/index.ts +19 -0
  247. package/src/lib/console-connect-options.ts +261 -0
  248. package/src/lib/console-telemetry-validate.ts +88 -0
  249. package/src/lib/flow-resolver.ts +117 -4
  250. package/src/lib/fs.ts +160 -0
  251. package/src/lib/local-artifact-root.ts +129 -3
  252. package/src/runtime-adapters.ts +113 -33
  253. package/src/tools/build-universal-bundles.ts +36 -2
  254. package/src/tools/codex-agent-routing.ts +48 -0
  255. package/src/tools/generate-context-map.ts +1 -0
  256. package/src/tools/validate-source-tree.ts +26 -1
@@ -0,0 +1,340 @@
1
+ # `kontour.learning-review-proposals` — kit/gate tuning proposal contract (v0.1)
2
+
3
+ **Status:** ratified (flow-agents #352). **Kind:** `kontour.learning-review-proposals`.
4
+ **Version:** `0.1`.
5
+
6
+ ## Purpose
7
+
8
+ Turn already-accumulated `kontour.console.economics` records (fa#349) into **advisory,
9
+ evidence-cited** kit/gate tuning proposals — the same "turn recorded outcomes into a proposal
10
+ a human ratifies" shape as `routing-efficiency.sh` (#415), generalized from per-`(role,model)`
11
+ to **per-kit** and **per-gate**. `scripts/telemetry/learning-review-proposals.sh` reads a
12
+ window of `economics.jsonl`, joins each record's kit/gate identity from already-shipped
13
+ sidecar shapes, computes aggregates, and emits proposals into a durable, idempotent ledger. A
14
+ companion script, `scripts/telemetry/learning-review-decide.sh`, records the human
15
+ ratify/reject/defer decision against a proposal id. Nothing here is ever auto-applied:
16
+ `learning-review` step 2b (see `kits/builder/skills/learning-review/SKILL.md`) surfaces the
17
+ output for review; a human decides.
18
+
19
+ This is a pure **consumer**: it makes zero edits to `economics-record.sh`,
20
+ `economics-record.schema.json`, or `gate-review`'s `InquiryRecord` output (ADR 0008/0010
21
+ consume-never-fork). It never writes to `kits/**`, `.datum/config.json`, or any gate/flow
22
+ config file.
23
+
24
+ ## Invocation
25
+
26
+ ```bash
27
+ scripts/telemetry/learning-review-proposals.sh [economics.jsonl ...] # defaults to the local economics log
28
+ cat economics.jsonl | scripts/telemetry/learning-review-proposals.sh - # records on stdin
29
+ scripts/telemetry/learning-review-proposals.sh --since 2026-07-01 --until 2026-07-07 \
30
+ --sessions-root .kontourai/flow-agents --ledger .kontourai/telemetry/learning-review-proposals.jsonl
31
+ ```
32
+
33
+ | Flag | Default | Meaning |
34
+ | --- | --- | --- |
35
+ | `--since VAL` | unbounded | date-only (`2026-07-01`), full ISO-8601 with `Z` (`2026-07-01T00:00:00Z`), full ISO-8601 with a `+00:00` UTC offset, or epoch-ms; lower bound on each record's `.at` |
36
+ | `--until VAL` | unbounded | same accepted formats; upper bound on each record's `.at` |
37
+ | `--sessions-root DIR` | `.kontourai/flow-agents` | where per-session `<task_slug>/trust.bundle` and `<task_slug>/gate-review.inquiries.json` are looked up for the kit/gate join |
38
+ | `--ledger PATH` | `.kontourai/telemetry/learning-review-proposals.jsonl` (override via `LEARNING_REVIEW_PROPOSALS_LEDGER` env, mirrors `TELEMETRY_ECONOMICS_LOG_FILE`) | the durable, idempotent proposal ledger |
39
+ | `-h`/`--help` | — | usage |
40
+
41
+ Positional args and stdin follow the exact `routing-efficiency.sh` convention: explicit file
42
+ args (a bare `-` means stdin), else stdin when piped, else the default log
43
+ (`${TELEMETRY_ECONOMICS_LOG_FILE:-${TELEMETRY_DATA_DIR:-<repo>}/.kontourai/telemetry/economics.jsonl}`).
44
+
45
+ **Accepted timestamp formats** (for `--since`/`--until` AND every record's own `.at`): an
46
+ epoch-ms integer (as a JSON number or a digit-only string); a date-only string (`YYYY-MM-DD`,
47
+ normalized to midnight UTC); a full ISO-8601 string with a literal `Z` UTC suffix; or a full
48
+ ISO-8601 string with an explicit `+00:00` UTC offset (converted internally to `Z` before
49
+ parsing — jq's `fromdateiso8601` only understands the `Z` form). Fractional seconds are
50
+ accepted and stripped before parsing (e.g. `2026-07-01T00:00:00.123Z`, the default shape of a
51
+ JS `Date.toISOString()` value) — precision finer than one second is not retained. Arbitrary
52
+ non-UTC offsets are not supported. A `--since`/`--until` value in none of these forms is a
53
+ CLI usage error: the analyzer exits non-zero with a stderr diagnostic and writes nothing,
54
+ never a fabricated `"insufficient-data"` result (an unrecognized CLI argument is operator-
55
+ correctable input, not a data gap). A record's own `.at` field, by contrast, degrades to an
56
+ unbounded (`null`) timestamp on an unrecognized value — the same honest-degradation treatment
57
+ as an absent `.at` — so one malformed record never aborts analysis of an otherwise-good window.
58
+
59
+ Tunables (env, all optional): `LR_MIN_WINDOW_SAMPLE` (5), `LR_MIN_KIT_SAMPLE` (6),
60
+ `LR_MIN_GATE_SAMPLE` (3), `LR_COST_RISE_PCT` (25), `LR_FLAT_FINDINGS_PCT` (10),
61
+ `LR_GATE_FALSE_BLOCK_RATE` (0.5), `LR_GATE_WELL_CALIBRATED_RATE` (0.9).
62
+
63
+ ## Output document shape
64
+
65
+ ```json
66
+ {
67
+ "schema": "kontour.learning-review-proposals",
68
+ "version": "0.1",
69
+ "window": { "since": "<epoch-ms|null>", "until": "<epoch-ms|null>" },
70
+ "records_considered": 0,
71
+ "outcome": "ok | insufficient-data",
72
+ "aggregates": {
73
+ "by_kit": [ { "kit_id": "...", "runs": 0, "...": "..." } ],
74
+ "by_gate": [ { "gate_id": "...", "fire_count": 0, "...": "..." } ],
75
+ "partial": false
76
+ },
77
+ "proposals": [
78
+ {
79
+ "proposal_id": "...",
80
+ "target": { "kind": "kit|gate", "id": "..." },
81
+ "pattern": "kit-review-cost-inflation | gate-false-block-review | gate-well-calibrated",
82
+ "proposed_change": "<human-readable recommendation>",
83
+ "severity": "advisory",
84
+ "evidence": { "cost": { "...": "..." }, "defect": { "...": "..." } },
85
+ "expected_effect": { "metric": "...", "direction": "decrease|increase|maintain", "description": "..." },
86
+ "decision": { "status": "proposed|ratified|rejected|deferred", "decided_by": null, "decided_at": null, "rationale": null },
87
+ "follow_on_ref": null,
88
+ "effect_observed": null,
89
+ "already_proposed": false
90
+ }
91
+ ],
92
+ "notes": [ "..." ]
93
+ }
94
+ ```
95
+
96
+ `window.since`/`window.until` in the OUTPUT document are the run's **effective** bounds:
97
+ the `--since`/`--until` arg when given, else respectively the min/max `.at` (epoch-ms,
98
+ normalized) actually observed among the records considered (or `null` when no records at
99
+ all). This makes the effect-fill chronological comparison (below) well-defined even when a
100
+ run is invoked with unbounded `--since`/`--until`.
101
+
102
+ `already_proposed` is an OUTPUT-ONLY annotation (not stored on the ledger line itself): `true`
103
+ when a proposal with the same `proposal_id` already exists in the ledger from a prior run (no
104
+ duplicate line is appended); `false` when this run appended a brand-new ledger line for it.
105
+
106
+ ## Kit/gate identity join (honest, reuses already-shipped shapes — no new field on fa#349's record)
107
+
108
+ For each economics record with a non-null `task_slug`, the analyzer looks for
109
+ `<sessions-root>/<task_slug>/trust.bundle` and `<sessions-root>/<task_slug>/gate-review.inquiries.json`
110
+ (both already-shipped, read-only inputs — never invoked, never modified):
111
+
112
+ - **`kit_id`** = the mode (most frequent) of `[.claims[].claimType | split(".")[0]]` from that
113
+ session's `trust.bundle` (the real Kit Catalog namespace, e.g. `builder`,
114
+ `veritas-governance` — `kits/<name>/` on disk). If the `trust.bundle` is absent, empty, or
115
+ unreadable, `kit_id` degrades to `"unattributed"` — the same honest-degradation idiom
116
+ `economics-record.sh` already uses for `phases[].phase`.
117
+ - **Per-gate rows** for that record = one row per `gate-review.inquiries.json` `InquiryRecord`:
118
+ `{ gate_id: .inquiry.target.fieldOrBehavior, outcome: .answer.value.calibration, fired: .answer.value.gateFired }`.
119
+ If the file is absent, empty, or unreadable, this record contributes zero gate rows (counted
120
+ separately in `notes` as "sessions without a gate-review join").
121
+ - A record with a **null** `task_slug` (or a `task_slug` with no matching session directory)
122
+ gets `kit_id = "unattributed"` directly, with zero gate rows contributed — no lookup is
123
+ attempted.
124
+
125
+ **Honest current-data gap (stated, not hidden):** real `economics.jsonl` records emitted today
126
+ all carry `"task_slug":null` (fa#349 slice-1; `task_slug` plumbing was never wired end-to-end
127
+ for the emitter's live invocation path). Until that separate, out-of-scope plumbing gap is
128
+ fixed, every real run's `kit_id` degrades to `"unattributed"` and it contributes zero gate
129
+ rows — per-kit/per-gate attribution is genuinely vacuous on live data today. This analyzer does
130
+ not fabricate an identity to paper over that gap; fixtures (not live data) are what exercise
131
+ the attributed path until `task_slug` is populated on real runs.
132
+
133
+ **Trusted-as-is inputs (consume-never-fork):** `cost.estimated_cost_usd` and every other numeric
134
+ field read from an `economics.jsonl` record is trusted exactly as fa#349 emitted it, including a
135
+ negative or zero value — this analyzer never clamps, floors, or re-derives it; a negative/zero
136
+ cost simply flows through the same average/percentage formulas as any other value (and can
137
+ legitimately produce a negative `cost_trend_pct`, which is not an error).
138
+
139
+ ## Window and aggregates (exact formulas)
140
+
141
+ All derived floating-point values (rates, percentages, dollar/second averages) are rounded to
142
+ **4 decimal places** using `(<value> * 10000 | round) / 10000` — the same rounding idiom
143
+ `routing-efficiency.sh` uses for `trouble_rate` (scaled to 4 places here instead of 3, for
144
+ dollar-amount precision). Any ratio with a zero or otherwise undefined denominator is `null`,
145
+ never a fabricated `0` or `Infinity`.
146
+
147
+ ### `by_kit[]`
148
+
149
+ Records are grouped by `kit_id`. Within a group, records are sorted ascending by `.at` and
150
+ split into a first half (oldest) and a second half (newest): `half_point = floor(runs / 2)`;
151
+ the first `half_point` records are the "first half," the remaining `runs - half_point` records
152
+ (the odd one out, if `runs` is odd, lands in the newer half) are the "second half."
153
+
154
+ | Field | Formula |
155
+ | --- | --- |
156
+ | `kit_id` | group key |
157
+ | `runs` | count of records in the group |
158
+ | `first_half_avg_cost_usd` | mean of `.cost.estimated_cost_usd` over the first half (`null` if the first half is empty) |
159
+ | `second_half_avg_cost_usd` | mean of `.cost.estimated_cost_usd` over the second half |
160
+ | `cost_trend_pct` | `((second_half_avg_cost_usd - first_half_avg_cost_usd) / first_half_avg_cost_usd) * 100`; `null` if `first_half_avg_cost_usd` is `null` or `0` |
161
+ | `first_half_findings_total` | sum of `.defects.findings_by_severity.{critical,high,medium,low}` over the first half |
162
+ | `second_half_findings_total` | same, over the second half |
163
+ | `findings_delta_pct` | `((second_half_findings_total - first_half_findings_total) / first_half_findings_total) * 100`; if `first_half_findings_total == 0`: `0` when `second_half_findings_total == 0` too, else `null` (a rise from a zero base has no defined percentage) |
164
+ | `avg_wall_clock_s` | mean of `.time.wall_clock_s` over ALL records in the group (not half-split) |
165
+ | `avg_human_wait_s` | mean of `.time.human_wait_s` over ALL records in the group |
166
+ | `route_back_rate` | `sum(.iterations.route_backs) / sum(.iterations.count)` over ALL records in the group; `null` if the count sum is `0` |
167
+ | `caught_false_completions_total` | sum of `.defects.caught_false_completions` over ALL records in the group |
168
+
169
+ ### `by_gate[]`
170
+
171
+ Gate rows from every considered record's join (above) are flattened and grouped by `gate_id`.
172
+
173
+ | Field | Formula |
174
+ | --- | --- |
175
+ | `gate_id` | group key (`inquiry.target.fieldOrBehavior`) |
176
+ | `fire_count` | total gate rows for this `gate_id` (every calibration: `correct` + `false_block` + `missed_block`) |
177
+ | `correct_count` | rows with `outcome == "correct"` |
178
+ | `false_block_count` | rows with `outcome == "false_block"` |
179
+ | `missed_block_count` | rows with `outcome == "missed_block"` |
180
+ | `false_block_rate` | `false_block_count / fire_count` |
181
+ | `avg_wall_clock_s_when_fired` | mean of the PARENT economics record's `.time.wall_clock_s`, over the distinct records that contributed at least one row for this `gate_id` with `fired == true` |
182
+ | `avg_human_wait_s_when_fired` | same, for `.time.human_wait_s` |
183
+
184
+ (`InquiryRecord`s themselves carry no wall-clock time; "when fired" averages borrow the
185
+ parent record's own time fields for exactly the records whose gate-review join produced a
186
+ `fired:true` row for that `gate_id`.)
187
+
188
+ ## Whole-window insufficient-data gate (AC4)
189
+
190
+ `records_considered` = the count of `kontour.console.economics` records whose `.at` falls in
191
+ `[since, until]` (both inclusive; unbounded when the flag is omitted). If
192
+ `records_considered < LR_MIN_WINDOW_SAMPLE` (default `5`): top-level `outcome` is
193
+ `"insufficient-data"`, `proposals` is `[]`, and `aggregates.partial` is `true` — but
194
+ `aggregates.by_kit`/`by_gate` are STILL computed and returned, for transparency (never hidden,
195
+ per `harness-capability-matrix.md`'s "degrade to an explicit unavailable, never fabricate").
196
+ This whole-window gate is distinct from, and evaluated in addition to, the per-target sample
197
+ gates (`LR_MIN_KIT_SAMPLE`/`LR_MIN_GATE_SAMPLE`) below, which just silently exclude a single
198
+ under-sampled target from proposal consideration without flipping the top-level `outcome`.
199
+
200
+ ## Proposal rules (Goodhart-paired: `evidence.cost` + `evidence.defect` always co-required)
201
+
202
+ Only evaluated when `outcome == "ok"` (i.e. the whole-window gate passed).
203
+
204
+ 1. **`kit-review-cost-inflation`** — fires when, for a `by_kit[]` entry: `runs >= LR_MIN_KIT_SAMPLE`
205
+ (default `6`) AND `cost_trend_pct >= LR_COST_RISE_PCT` (default `25`) AND
206
+ `findings_delta_pct <= LR_FLAT_FINDINGS_PCT` (default `10`) — cost is rising while defects
207
+ caught are flat or falling (the pattern a Goodhart-blind cost-only metric would miss).
208
+ - `evidence.cost = { first_half_avg_cost_usd, second_half_avg_cost_usd, cost_trend_pct }`
209
+ - `evidence.defect = { first_half_findings_total, second_half_findings_total, findings_delta_pct, caught_false_completions_total }`
210
+ - `expected_effect = { metric: "avg_cost_usd", direction: "decrease", description: "cost should come back down without a corresponding rise in escaped defects" }`
211
+ 2. **`gate-false-block-review`** — fires when, for a `by_gate[]` entry:
212
+ `fire_count >= LR_MIN_GATE_SAMPLE` (default `3`) AND `false_block_rate >= LR_GATE_FALSE_BLOCK_RATE`
213
+ (default `0.5`) — the gate is blocking on already-passing claims more often than not.
214
+ - `evidence.cost = { avg_wall_clock_s_when_fired, avg_human_wait_s_when_fired }`
215
+ - `evidence.defect = { fire_count, correct_count, false_block_count, missed_block_count }`
216
+ - `expected_effect = { metric: "false_block_rate", direction: "decrease", description: "false_block_rate should fall toward correctly-calibrated blocking" }`
217
+ 3. **`gate-well-calibrated`** (monitor-style, still `severity:"advisory"` — mirrors
218
+ `routing-efficiency.sh`'s `keep-tier`) — fires when `fire_count >= LR_MIN_GATE_SAMPLE` AND
219
+ `(correct_count / fire_count) >= LR_GATE_WELL_CALIBRATED_RATE` (default `0.9`) AND
220
+ `false_block_count == 0` AND `missed_block_count == 0`.
221
+ - `evidence.cost = { avg_wall_clock_s_when_fired, avg_human_wait_s_when_fired }`
222
+ - `evidence.defect = { fire_count, correct_count, false_block_count, missed_block_count }`
223
+ - `expected_effect = { metric: "false_block_rate", direction: "maintain", description: "gate is well-calibrated; no change expected or needed" }`
224
+
225
+ A `by_kit`/`by_gate` entry under its rule's sample-size gate contributes **no proposal at
226
+ all** for that rule (silently excluded from proposals, still visible in `aggregates`).
227
+
228
+ Every proposal's `evidence` object structurally REQUIRES both `cost` and `defect`
229
+ (`learning-review-proposals.schema.json`, mirroring fa#349's own `cost`+`defects`
230
+ co-requirement, R7) — a proposal citing cost alone (or defect alone) is schema-invalid. This
231
+ is the Goodhart guard: no proposal may claim "cheaper" without also showing "and here is what
232
+ it caught or missed."
233
+
234
+ ## Idempotent ledger (`.kontourai/telemetry/learning-review-proposals.jsonl`)
235
+
236
+ Durable, append-mostly JSONL — one proposal per line, in the exact shape above (minus the
237
+ output-only `already_proposed` field). Default path
238
+ `.kontourai/telemetry/learning-review-proposals.jsonl`, override via `--ledger PATH` or
239
+ `LEARNING_REVIEW_PROPOSALS_LEDGER` (mirrors `TELEMETRY_ECONOMICS_LOG_FILE`'s local-first
240
+ convention: the per-run snapshot lives in the session artifact dir; this ledger is the durable,
241
+ cross-session trail).
242
+
243
+ - **`proposal_id`** = `slugify("<since>_<until>_<target_kind>-<target_id>_<pattern>")`, where
244
+ `<since>`/`<until>` are the raw `--since`/`--until` argument strings (or the literal `all`
245
+ when the flag was omitted) and `slugify()` is the SAME idiom already used at
246
+ `evals/ci/run-baseline.sh:139-140`:
247
+ `tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-//; s/-$//'` — no external hash
248
+ dependency.
249
+ - On every run: proposals are computed fresh (stateless — the analyzer never reads its own
250
+ ledger to decide WHAT to propose, only to decide whether a computed proposal is new). For
251
+ each computed proposal, the ledger is checked for an existing line with the same
252
+ `proposal_id`:
253
+ - **Found** → mark `already_proposed:true` in this run's OUTPUT; no duplicate line is
254
+ appended.
255
+ - **Not found** → append a new ledger line with
256
+ `decision:{status:"proposed",decided_by:null,decided_at:null,rationale:null}`,
257
+ `follow_on_ref:null`, `effect_observed:null`, and mark `already_proposed:false` in the
258
+ output.
259
+ - Running the analyzer twice over the identical window therefore produces zero duplicate
260
+ `proposal_id`s in the ledger (AC4) — the second run's output marks every proposal
261
+ `already_proposed:true`.
262
+
263
+ ## Decision recording (`learning-review-decide.sh`)
264
+
265
+ ```bash
266
+ scripts/telemetry/learning-review-decide.sh <ledger-path> <proposal-id> \
267
+ --ratify|--reject|--defer --decided-by NAME [--rationale TEXT] [--follow-on-ref REF]
268
+ ```
269
+
270
+ Finds the ledger line with the matching `proposal_id` and rewrites its `decision` object
271
+ (`status`, `decided_by`, `decided_at: <now, ISO-8601>`, `rationale`) in place, atomically
272
+ (temp file + `mv`). `follow_on_ref` is set ONLY when `--ratify` and `--follow-on-ref` are both
273
+ present. Exits non-zero, writing nothing, when: the `proposal_id` is not found; more than one
274
+ of `--ratify`/`--reject`/`--defer` is passed; or `--follow-on-ref` is passed without `--ratify`
275
+ (the AC3 ratify-before-follow-on ordering guard — a follow-on work item can never be linked to
276
+ an unratified proposal). All other ledger lines are left byte-identical.
277
+
278
+ ## Effect-fill pass (AC5)
279
+
280
+ On every `learning-review-proposals.sh` invocation, AFTER computing and ledgering the current
281
+ window's proposals: scan the ledger for entries where `decision.status == "ratified"` AND
282
+ `effect_observed == null` AND the entry's `window.until` (epoch-ms) is chronologically before
283
+ the CURRENT invocation's effective `window.since` (epoch-ms) — i.e. the ratified proposal's
284
+ own window has fully elapsed before this new window begins. For each match, recompute the
285
+ metric named in that entry's `expected_effect.metric` over the CURRENT window's aggregates,
286
+ for the exact same `target`:
287
+
288
+ - `target.kind == "kit"` → `after` = the current window's `by_kit[]` entry for that `kit_id`'s
289
+ overall average cost (`second_half_avg_cost_usd`, i.e. the newest-half average of the new
290
+ window — comparable apples-to-apples against the value that triggered the original
291
+ proposal); `before` = the ratified proposal's own `evidence.cost.second_half_avg_cost_usd`
292
+ (the elevated level that triggered it).
293
+ - `target.kind == "gate"` → `after` = the current window's `by_gate[]` entry's
294
+ `false_block_rate` for that `gate_id`; `before` = the ratified proposal's own
295
+ `evidence.defect.false_block_count / evidence.defect.fire_count`.
296
+
297
+ If no `by_kit`/`by_gate` entry exists for that target in the current window (e.g. the kit/gate
298
+ had zero runs this window), `effect_observed` is left `null` this pass — an honest gap, not a
299
+ fabricated measurement; it is retried on the next invocation that does have data.
300
+
301
+ Writes:
302
+
303
+ ```json
304
+ "effect_observed": {
305
+ "measured_at": "<ISO-8601, now>",
306
+ "metric": "avg_cost_usd | false_block_rate",
307
+ "before": 0.0,
308
+ "after": 0.0,
309
+ "moved": "improved | worsened | unchanged"
310
+ }
311
+ ```
312
+
313
+ `moved` is derived from `expected_effect.direction`: `"improved"` = the metric moved in the
314
+ direction the proposal intended (`decrease` → `after < before`; `increase` → `after > before`);
315
+ `"unchanged"` = `after == before` exactly; `"worsened"` = moved, but the wrong way (or any
316
+ non-zero move when `direction == "maintain"`).
317
+
318
+ ## Guardrails (ADR 0008/0010, ADR 0002/0003 call 5)
319
+
320
+ - **Advisory only.** Every proposal is `severity: "advisory"`. Neither
321
+ `learning-review-proposals.sh` nor `learning-review-decide.sh` EVER writes to `kits/**`,
322
+ `.datum/config.json`, or any gate/flow config file — they only read economics/session
323
+ artifacts and write to their own ledger.
324
+ - **Human-ratified, decision-before-follow-on.** A recorded human decision
325
+ (`learning-review-decide.sh`) is required before any follow-on work item may cite a
326
+ proposal; `--follow-on-ref` is refused without `--ratify` (AC3).
327
+ - **Consume-never-fork.** `economics-record.schema.json` (fa#349) and
328
+ `gate-review.inquiries.json` (#118-120) are read-only inputs, never modified, never
329
+ re-derived by inventing a parallel schema.
330
+ - **No fabrication.** Ratios with an undefined denominator are `null`, never `0`; an
331
+ under-threshold window yields an explicit `"insufficient-data"` outcome, never a confident
332
+ but thin call; real records lacking `task_slug` degrade to `"unattributed"`, never an
333
+ invented kit identity.
334
+
335
+ ## Relationship to `routing-efficiency.sh` (#415)
336
+
337
+ Same architecture, same input stream (`economics.jsonl`), same guardrails — generalized from
338
+ per-`(role,model)` delegation efficiency to per-kit/per-gate review efficiency. Both are purely
339
+ manual/skill-invoked (no scheduler/cron primitive exists in this repo for either); both are
340
+ wired into `learning-review`'s closeout step (2a for routing, 2b for this contract).
@@ -0,0 +1,59 @@
1
+ # Routing-efficiency review
2
+
3
+ **Turn recorded delegation outcomes into advisory proposals about which model each role should route
4
+ to — the #409 small-model value proof, pointed inward at our own agent fan-out (#415).**
5
+
6
+ `scripts/telemetry/routing-efficiency.sh` reads the per-run economics records (`delegations[]` +
7
+ `outcome`, from `economics-record.sh`) and, per `(role, model)`, asks: *is the model this role routes
8
+ to actually efficient, or is a cheaper tier being re-worked so often it should be escalated (or an
9
+ expensive tier so reliably accepted it is over-provisioned)?* `learning-review` runs it at closeout
10
+ (step 2a) and a human ratifies any change.
11
+
12
+ ## Input / output
13
+
14
+ ```bash
15
+ routing-efficiency.sh [economics.jsonl ...] # defaults to .kontourai/telemetry/economics.jsonl
16
+ cat economics.jsonl | routing-efficiency.sh - # or records on stdin
17
+ ```
18
+
19
+ Emits a `kontour.routing-efficiency` v0.1 document: `by_role_model[]` (per-group outcome counts +
20
+ `trouble_rate`) and `proposals[]` (one advisory proposal per group), plus `notes`.
21
+
22
+ ## What it computes (honest by construction)
23
+
24
+ For each `(role, bare-model)` group across all delegations:
25
+
26
+ - `measurable = accepted + rework + diverged + failed` — **`unavailable` outcomes are excluded**. A
27
+ delegation whose outcome the harness could not expose is neither a success nor a failure, so it never
28
+ moves a rate (see `harness-capability-matrix.md`). It is counted separately as coverage.
29
+ - `trouble_rate = (rework + diverged + failed) / measurable` — the share of *measurable* delegations
30
+ that did not cleanly accept.
31
+
32
+ ## Proposal kinds
33
+
34
+ | Kind | Fires when | Meaning |
35
+ | --- | --- | --- |
36
+ | `escalate-minimum-tier` | `measurable ≥ ROUTING_MIN_SAMPLE` and `trouble_rate ≥ ROUTING_HIGH_TROUBLE` (0.5) | the cheap tier is under-routed for this role — consider raising its minimum tier |
37
+ | `keep-tier` | `measurable ≥ min` and `trouble_rate ≤ ROUTING_LOW_TROUBLE` (0.1) | efficient — accepts cleanly; keep current routing (a candidate to try a *cheaper* tier lives in future work) |
38
+ | `monitor` | `measurable ≥ min`, trouble in between | within tolerance; watch |
39
+ | `insufficient-signal` | `measurable < min` | not enough measurable outcomes to judge — coverage reported, no confident call |
40
+
41
+ Thresholds are tunable via env: `ROUTING_MIN_SAMPLE` (default 3), `ROUTING_HIGH_TROUBLE` (0.5),
42
+ `ROUTING_LOW_TROUBLE` (0.1).
43
+
44
+ ## Guardrails (ADR 0002 / ADR 0003 call 5)
45
+
46
+ - **Advisory only.** Every proposal is `severity: "advisory"`. The analyzer NEVER edits
47
+ `.datum/config.json` or any routing config — it only reads and reports.
48
+ - **Human-ratified.** A person decides whether to act on a proposal; the resulting role→model change
49
+ travels the normal deliver loop, never an auto-apply.
50
+ - **No fabrication.** `unavailable` outcomes are excluded from rates; a thin sample yields
51
+ `insufficient-signal`, not a confident verdict; empty input yields an empty `proposals[]`, never an
52
+ invented lesson.
53
+
54
+ ## Relationship to the value plane
55
+
56
+ This is the same question the external value proof (`kontourai/evals`, #409) asks — "does routing the
57
+ right model to each task pay off?" — asked of our *internal* delegation. The console renders the
58
+ `(role, model)` cost + acceptance surface (#415 console panel); this analyzer produces the actionable
59
+ proposal that closes the loop back to the routing ladder.
@@ -1,31 +1,38 @@
1
- # Verifiable Trust — why "done" actually means done
1
+ # Verifiable Trust — tamper-evident done
2
2
 
3
3
  > **The problem with autonomous coding agents: they grade their own homework.**
4
4
  > An agent writes the code, runs the tests, and reports "all green, shipped." If it's
5
5
  > wrong — or if it learns it can just *say* the tests passed — you find out in production.
6
- > Flow Agents is built so an agent **can't** mark work complete that isn't.
6
+ > Flow Agents is built so "done" is a claim with evidence, not a status the model gets
7
+ > to assert.
7
8
 
8
9
  ## The one-line pitch
9
10
 
10
11
  Flow Agents treats "the work is done" as a **claim that must be proven**, not a status the
11
- agent gets to assert. Completion is gated by **evidence the system re-derives itself**, and
12
- the authoritative check runs in **CI — an environment the agent can't disable or fake** —
13
- with **cryptographically signed provenance** of exactly what was verified.
12
+ agent gets to assert. Completion is gated by evidence the system re-derives, with two
13
+ different trust tiers:
14
14
 
15
- Most agent frameworks trust the model's self-report. Flow Agents doesn't trust the agent,
16
- its claims, *or* its environment.
15
+ - **L1 local runtime gate and capture** advisory, best-effort protection inside the agent
16
+ runtime. It catches native command evidence, stop-short behavior, and obvious divergence
17
+ before the work leaves the session.
18
+ - **L2 controlled re-run anchor** — CI re-executes manifest commands and reconciles them
19
+ against git diff as ground truth. This is the authoritative boundary for CI-verified
20
+ status.
21
+
22
+ Most agent frameworks trust the model's self-report. Flow Agents treats that self-report as
23
+ input to reconcile, not as truth. The posture is **tamper-evident**, not tamper-proof.
17
24
 
18
25
  ## What that buys you
19
26
 
20
- - **"Done" you can rely on.** A finished task ends with real evidence — tests, build, lint,
21
- review findings, captured command results and the gate *re-derives* the verdict from
22
- that evidence. A claimed pass that contradicts a captured failure is **blocked**, not shipped.
23
- - **Anti-gaming by design.** The gate independently captures real command results and
24
- reconciles them against the agent's claims namespace-agnostic, and independent of any
25
- status the agent self-declares. Tricks like `npm test || true` (laundering the exit code)
26
- are rejected.
27
- - **An external anchor that can't be switched off.** On every pull request, CI re-runs the
28
- verification *fresh* in a clean environment and **fails the merge on any divergence**
27
+ - **"Done" you can inspect.** A finished task ends with evidence — tests, build, lint,
28
+ review findings, captured command results, CI output, or an explicit `NOT_VERIFIED` gap.
29
+ A claimed pass that contradicts captured or reconciled evidence is blocked or downgraded,
30
+ not laundered into verified status.
31
+ - **Anti-gaming by design.** Local capture raises the cost of direct native-command
32
+ self-tampering, and CI reconciles manifest commands against fresh results. Tricks like
33
+ `npm test || true` are rejected when they are represented as command evidence.
34
+ - **An external anchor that can't be switched off locally.** On pull requests wired to the
35
+ trust anchor, CI re-runs verification fresh in a clean environment and fails on divergence
29
36
  between what the agent claimed and what CI actually observed. The agent can tamper with its
30
37
  own machine all it likes; it can't reach into CI.
31
38
  - **Signed provenance.** CI mints a Sigstore-signed attestation over its *own* results. The
@@ -46,7 +53,7 @@ its claims, *or* its environment.
46
53
  reproducible record of *what was verified and how* — provenance you can hand to an auditor,
47
54
  not a screenshot of a passing run.
48
55
  - **Multi-agent and at-scale delivery.** Every agent's output is held to the same external,
49
- un-gameable bar — so you can fan out work without fanning out the risk that one agent
56
+ evidence-backed bar — so you can fan out work without fanning out the risk that one agent
50
57
  learns to game its gate.
51
58
  - **Platform teams adopting agents.** Add the trust anchor as a required check in your repos
52
59
  and get a consistent, enforced "agents must prove it" policy across every team.
@@ -55,20 +62,62 @@ its claims, *or* its environment.
55
62
 
56
63
  | | Typical agent setup | Flow Agents |
57
64
  |---|---|---|
58
- | "Is it done?" | The agent says so | Re-derived from independent evidence |
59
- | Failure hiding | Easy (claim pass, launder exit codes) | Caught captured results reconcile against claims |
60
- | Where trust lives | In the agent's environment | **External** CI re-runs fresh, agent can't disable it |
65
+ | "Is it done?" | The agent says so | Re-derived from evidence; CI-verified only after the controlled anchor reconciles |
66
+ | Failure hiding | Easy (claim pass, launder exit codes) | Native command capture locally; manifest-command reconciliation in CI |
67
+ | Where trust lives | In the agent's environment | Local tier is advisory; authoritative tier is **external** CI + git ground truth |
61
68
  | Provenance | A log line | **Sigstore-signed** attestation of CI's own results |
62
69
  | Tampering with the gate | Possible | Required tests + code-owner review block it |
63
70
 
71
+ ## Trust tiers
72
+
73
+ ### L1: local runtime gate and capture
74
+
75
+ The local tier runs inside the agent's runtime. It can steer the workflow, capture native
76
+ command results, detect stop-short behavior, and warn or block depending on the host's hook
77
+ surface. It is useful because it catches problems early, while the agent is still in the
78
+ session.
79
+
80
+ It is not the authoritative trust boundary. The agent and its tools can still affect the
81
+ local machine. Local evidence is treated as session-local until it is either reconciled by
82
+ the controlled anchor or explicitly carried as a lower-trust attestation.
83
+
84
+ ### L2: controlled CI re-run
85
+
86
+ The controlled anchor runs outside the agent session. CI re-executes the manifest commands,
87
+ checks the resulting status against the agent's claims, and uses git diff as the ground truth
88
+ for what changed. A write that bypassed local capture still appears in the diff. A command
89
+ claim that cannot be tied to a manifest command re-run does not become CI-verified just
90
+ because the agent said it passed.
91
+
92
+ The CI tier is where evidence can become independently reconciled. That is the line between
93
+ "the session reported this" and "the controlled environment re-ran or inspected this."
94
+
95
+ ## Threat model note
96
+
97
+ The local tier keys on native command tools. MCP or other non-native tool effects can bypass
98
+ local capture, including file writes and test runs.
99
+
100
+ That blind spot is disclosed, not hidden:
101
+
102
+ - **File writes** still hit the CI/git boundary because the pull request diff is ground
103
+ truth for delivered changes.
104
+ - **Non-native test runs** do not become CI-verified evidence unless the same check is
105
+ represented by a manifest command that CI re-runs. Otherwise they remain session-local or
106
+ `NOT_VERIFIED`.
107
+ - **Bypassed local capture** should refuse, escalate, or degrade confidence. It must not be
108
+ laundered into a verified CI claim.
109
+
110
+ Every trust claim should be traceable to public artifacts: the recorded evidence, the
111
+ manifest command, the CI run, the git diff, or an explicit verification gap.
112
+
64
113
  ## The honest part
65
114
 
66
115
  This is a **defense-in-depth bar-raiser, not a magic wall** — and the docs say so plainly.
67
- The local gate raises the cost of casual or direct self-tampering; the *real* tamper-proof
68
- boundary is **external**: CI's fresh re-run, the CI-minted signatures, and human (owner)
69
- review none of which the agent can reach. Known residuals (and their mitigations) are
70
- documented openly rather than hidden, because overstating security is its own risk. We'd
71
- rather you trust this *because* you can see where the lines are.
116
+ The local gate raises the cost of casual or direct self-tampering; the real authoritative
117
+ boundary is **external**: CI's fresh re-run, git diff reconciliation, CI-minted signatures,
118
+ and human (owner) review. Known residuals are documented openly rather than hidden, because
119
+ overstating security is its own risk. We'd rather you trust this *because* you can see where
120
+ the lines are.
72
121
 
73
122
  > This posture wasn't designed on paper and declared safe — it was **earned by an adversarial
74
123
  > loop**: independent reviewers repeatedly tried to defeat the gate (and found real holes we
@@ -201,6 +201,8 @@ Plans should preserve the work item's `R*` and `AC*` ids from `idea-to-backlog`.
201
201
 
202
202
  Keep an isolated worktree alive through review, verification, provider change creation, and provider checks so the branch remains inspectable and fixable without disturbing the main checkout. Remove the worktree only after the change is merged or accepted, the branch is abandoned, or the user explicitly asks to collapse the isolation. Do not copy files back into the original checkout by hand; Git is the merge surface.
203
203
 
204
+ After creating or entering a fresh worktree, run `npm ci` there before pre-push checks or evals; each worktree needs its own `node_modules`.
205
+
204
206
  When `pull-work` chooses a worktree, record `worktree_lifecycle`: path, branch, retain-until condition, cleanup owner, cleanup command, and cleanup blockers. Publish-change retains the worktree for review/CI fixes; final acceptance or explicit abandonment owns cleanup.
205
207
 
206
208
  Example prompt:
@@ -581,6 +583,14 @@ Use learning-review. Capture facts, decisions, gaps, follow-ups, and durable kno
581
583
 
582
584
  ## Resumable sessions
583
585
 
586
+ Builder build sessions also project their canonical Flow run into the resume block.
587
+ Start a newly selected session with `flow-agents builder-run start --session-dir
588
+ .kontourai/flow-agents/<slug>`. Thereafter, follow `next_action.skills` or
589
+ `next_action.operations`, record the current gate evidence, and use the exact
590
+ `next_action.command` if automatic synchronization was interrupted. See
591
+ [`docs/spec/builder-flow-runtime.md`](spec/builder-flow-runtime.md) for the ownership,
592
+ trust-binding, route-back, and artifact-root contract.
593
+
584
594
  When a session resumes (after context compaction, an agent restart, or a cross-session
585
595
  handoff), the workflow-steering hook emits a `RESUME:` block on `SessionStart` that
586
596
  gives the resuming agent immediate situational awareness without blocking or auto-deciding.