@kontourai/flow-agents 3.3.0 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +48 -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 +56 -3
  34. package/build/src/lib/flow-resolver.js +151 -11
  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 +101 -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 +153 -10
  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,270 @@
1
+ # `kontour.console.economics` — per-run economics record contract (v0.1)
2
+
3
+ **Status:** ratified (flow-agents #349). **Kind:** `kontour.console.economics`. **Version:** `0.1`.
4
+
5
+ ## Purpose
6
+
7
+ Every kit-driven run emits exactly one **per-run economics record** — cost, time,
8
+ iterations, and defects caught — so that "flow kits save money and produce more accurate
9
+ results" is a **measurable, falsifiable** claim backed by data. This record is the
10
+ measurement substrate for the Kit-economics telemetry initiative (I32–I35): it is
11
+ consumed by the baseline harness (#350), the small-model headline (#409), and the console
12
+ value view (console #117).
13
+
14
+ ## Architecture (console ADR 0003)
15
+
16
+ - **Call 1 — additive kind on one pipe.** `kontour.console.economics` is a new *versioned
17
+ record KIND* that rides the single authenticated ingress (`ApiSink`, `POST /records`)
18
+ alongside `kontour.console.event`, `.projection`, and `.liveness`. It is **never** a new
19
+ endpoint or a new auth path.
20
+ - **Call 2 — tenant is bound from the verified principal.** The `ApiSink` stamps the
21
+ authoritative tenant from the request principal. The emitter MAY carry `tenant_id`
22
+ (from `CONSOLE_TENANT_ID`) for **self-description only**; the emitter is never the source
23
+ of truth for tenancy.
24
+ - **Call 3 — immutable fact, rollups are projections.** This record is an **immutable
25
+ per-run fact**. The console-side economics rollups and the value view are **rebuildable
26
+ projections** over the stream of these records. flow-agents emits facts, never a rollup;
27
+ this emitter computes **no cross-run aggregate**.
28
+ - **Call 6 — local-first, best-effort.** The record is **always** written to the local log
29
+ channel first; the console POST is a **detached, fail-open** fire that can never block or
30
+ fail a run. Every failure path is a quiet `exit 0`.
31
+
32
+ ## Record shape
33
+
34
+ ```json
35
+ {
36
+ "schema": "kontour.console.economics",
37
+ "version": "0.1",
38
+ "run_id": "string",
39
+ "at": "epoch-millis string",
40
+ "task_slug": "string|null",
41
+ "model": "string",
42
+ "pricing_version": "string|null",
43
+
44
+ "cost": {
45
+ "input_tokens": 0,
46
+ "output_tokens": 0,
47
+ "cache_creation_input_tokens": 0,
48
+ "cache_read_input_tokens": 0,
49
+ "estimated_cost_usd": 0.0,
50
+ "by_model": []
51
+ },
52
+
53
+ "time": {
54
+ "wall_clock_s": 0,
55
+ "human_wait_s": 0
56
+ },
57
+
58
+ "phases": [
59
+ { "phase": "plan|execute|review|verify|unattributed",
60
+ "input_tokens": 0, "output_tokens": 0,
61
+ "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0,
62
+ "estimated_cost_usd": 0.0, "wall_clock_s": 0 }
63
+ ],
64
+
65
+ "iterations": {
66
+ "count": 1,
67
+ "route_backs": 0
68
+ },
69
+
70
+ "defects": {
71
+ "gate_fires": 0,
72
+ "findings_by_severity": { "critical": 0, "high": 0, "medium": 0, "low": 0 },
73
+ "caught_false_completions": 0,
74
+ "verification_verdict": "PASS|FAIL|NOT_VERIFIED"
75
+ },
76
+
77
+ "tenant_id": "string|null"
78
+ }
79
+ ```
80
+
81
+ ## Field sources
82
+
83
+ Every field derives from a named telemetry event, usage field, or sidecar file. The emitter
84
+ **never re-estimates tokens** — ground truth is the transcript's `.message.usage` blocks,
85
+ already parsed by `usage_parse_transcript` (scripts/telemetry/lib/usage.sh) and carried on
86
+ the `session.usage` event.
87
+
88
+ | Field | Type | Source |
89
+ | --- | --- | --- |
90
+ | `schema` | literal | `"kontour.console.economics"` |
91
+ | `version` | literal | `"0.1"` |
92
+ | `run_id` | string | `session.usage .session_id` |
93
+ | `at` | epoch-millis string | `session.usage .timestamp` (session end) |
94
+ | `task_slug` | string\|null | `state.json .task_slug` |
95
+ | `model` | string | `session.usage .usage.model` |
96
+ | `pricing_version` | string\|null | `session.usage .usage.pricing_version` (from `pricing.json` `current_version`) |
97
+ | `cost.input_tokens` | int | `session.usage .usage.input_tokens` (transcript ground truth) |
98
+ | `cost.output_tokens` | int | `session.usage .usage.output_tokens` |
99
+ | `cost.cache_creation_input_tokens` | int | `session.usage .usage.cache_creation_input_tokens` |
100
+ | `cost.cache_read_input_tokens` | int | `session.usage .usage.cache_read_input_tokens` |
101
+ | `cost.estimated_cost_usd` | number | `session.usage .usage.estimated_cost_usd` (derived; recomputable console-side from `pricing_version`) |
102
+ | `cost.by_model` | array | `session.usage .usage.by_model`, verbatim |
103
+ | `time.wall_clock_s` | int | `session.usage .usage.duration_s` |
104
+ | `time.human_wait_s` | int | elapsed time blocked on a human decision (slice-1: `0` if uninstrumented; **never omit the key**) |
105
+ | `phases[]` | array | per-phase attribution — see the phase-sum invariant below |
106
+ | `iterations.count` | int | deliver-loop passes (plan→execute→review→verify); slice-1 default `1` |
107
+ | `iterations.route_backs` | int | verify FAIL → re-plan/re-execute loops; slice-1 default `0` |
108
+ | `defects.gate_fires` | int | count of gate fires in the run |
109
+ | `defects.findings_by_severity` | object | grouped from `critique.json .critiques[].findings[]` on `.severity` (missing → `low`) |
110
+ | `defects.caught_false_completions` | int | claimed-pass ACs contradicted by trusted-backstop re-runs (DISTINCT counter) |
111
+ | `defects.verification_verdict` | enum | final verify-work verdict from the sidecar (`PASS`\|`FAIL`\|`NOT_VERIFIED`) |
112
+ | `delegations[]` | array | per-sub-agent delegation facts + derived outcome (#415) — see below; `[]` when `--agents-dir` is absent |
113
+ | `signals` | object | harness-capability declaration — what telemetry this runtime exposed (see below + `harness-capability-matrix.md`) |
114
+ | `tenant_id` | string\|null | self-description only; the `ApiSink` stamps the authoritative tenant (ADR 0003 call 2) |
115
+
116
+ ## `delegations[]` — per-sub-agent routing facts + outcome (#415)
117
+
118
+ When the emitter is given `--agents-dir <slug>/agents`, it assembles one entry per delegated
119
+ sub-agent, joined from each `<slug>/agents/<agent-id>/events.jsonl`:
120
+
121
+ | Field | Type | Source |
122
+ | --- | --- | --- |
123
+ | `agent_id` | string\|null | the sub-agent id (join key) |
124
+ | `role` | string | routing role recorded on the delegation event (`delegate-mechanical`\|`delegate-implementation`\|`delegate-design`\|…) |
125
+ | `resolved_model` | string | the model that role resolved to (`.datum/config.json`), e.g. `claude-haiku-4-5@anthropic` |
126
+ | `summary` | string\|null | the delegation/escalation event's free-text summary (stands in for a structured task_type) |
127
+ | `escalated_from` | string | present only when the sub-agent escalated: the lower tier it was promoted from |
128
+ | `dispatch_count` | int | how many times the orchestrator (re)dispatched this agent_id (delegation + escalation events); `>1` = re-prompted |
129
+ | `outcome` | enum | `accepted`\|`rework`\|`diverged`\|`failed`\|`unavailable` — derived (see below) |
130
+
131
+ **Assembly rule:** all events for an `agent_id` are grouped; role/model come from the **latest**
132
+ `delegation`/`escalation` event (an escalation supersedes and carries `escalated_from`). Any read/parse
133
+ failure degrades to `[]` — never fatal (local-first, best-effort).
134
+
135
+ **Outcome — derived only from ORCHESTRATOR-OBSERVABLE signals, never fabricated.** The orchestrator
136
+ knows what it dispatched, how often it re-dispatched, and how it corrected — so outcome holds **without
137
+ peeking inside the sub-agent** (which most harnesses forbid — see `harness-capability-matrix.md`):
138
+
139
+ - `diverged` — an explicit supersession marker (`kind:"supersession"` or `status:"diverged"`) exists.
140
+ - `rework` — an escalation happened **or** the orchestrator re-dispatched the agent (`dispatch_count > 1`).
141
+ - `failed` — the latest terminal verdict event (`kind` `evidence`/`verdict`) is a FAIL.
142
+ - `accepted` — the latest terminal verdict is a PASS (and no escalation / re-dispatch / supersession).
143
+ - `unavailable` — no terminal verdict was recorded on this harness. **Not assumed `accepted`** — absence
144
+ of a verdict is not evidence of success.
145
+
146
+ **Per-delegation COST is still not carried here.** Token usage is *sub-agent-internal* and no runtime
147
+ isolates it today (`signals.per_delegation_tokens = false`), so a per-delegation cost split would be
148
+ fabrication. Cost per `(role, model)` is a **console projection** — join `delegations[]` (role→model)
149
+ against `cost.by_model` (1:1 with roles under the current `.datum/config.json`), labeled model-granularity.
150
+
151
+ ## `signals` — harness-capability declaration
152
+
153
+ Declares what telemetry the emitting runtime actually exposed, so a consumer distinguishes a real zero
154
+ from a harness-blind gap (full doctrine + per-runtime matrix in `harness-capability-matrix.md`):
155
+
156
+ | Field | Meaning |
157
+ | --- | --- |
158
+ | `runtime` | the runtime that produced the record (`claude-code`, `kiro-cli`, …), from `session.usage .agent.runtime` |
159
+ | `per_delegation_tokens` | `true` iff the runtime isolates per-sub-agent tokens. `false` everywhere today → per-delegation cost unavailable |
160
+ | `per_delegation_outcome` | outcome-signal coverage this run: `full`\|`partial`\|`none`\|`n/a` |
161
+
162
+ Consumers MUST read `signals` before rendering a delegation metric: if the needed signal is unavailable,
163
+ show "not measurable on this harness," never a misleading number.
164
+
165
+ ## R7 Goodhart guard (structural, hard requirement)
166
+
167
+ `cost` and `defects` are **co-required** in the JSON Schema
168
+ (`"required": ["schema","version","run_id","cost","time","iterations","defects"]`). A record
169
+ with `cost` but no `defects` block is **schema-invalid**. This is structural: **no consumer
170
+ can render "cheaper" without also rendering "and here is what it caught / missed."** Cost is
171
+ only meaningful paired with the defect signal it traded against — the Goodhart pair.
172
+
173
+ ## Phase-sum invariant
174
+
175
+ The sum over `phases[].input_tokens` (and each other token / cost / wall field) MUST equal
176
+ the corresponding top-level `cost` / `time` total. When no phase context exists in
177
+ `state.json`, **all** amounts land in a single `{"phase":"unattributed", ...}` entry — never
178
+ silently dropped or misattributed. The eval asserts this invariant for both the
179
+ phase-known and phase-unknown fixtures.
180
+
181
+ ## Version semantics
182
+
183
+ `version` is the record-shape version, independent of `pricing_version` (which versions the
184
+ cost derivation). A shape change bumps `version`; a pricing change bumps `pricing_version`.
185
+ Because the record stream is immutable and tenant-stamped, shape evolution is a
186
+ **re-projection** console-side, not a migration of authority.
187
+
188
+ ## Emission (local-first, best-effort)
189
+
190
+ 1. The record is assembled with a **single `jq -c` filter** so every untrusted field
191
+ (`task_slug`, model names, finding text) is `\u`-escaped — JSON is never string-built.
192
+ 2. The record is written to the local economics log
193
+ (`${TELEMETRY_ECONOMICS_LOG_FILE:-${TELEMETRY_DATA_DIR}/economics.jsonl}`) **first** — the
194
+ fixed, non-doubled path (#469; `TELEMETRY_DATA_DIR` is already the fully-qualified
195
+ `.../.kontourai/telemetry` data dir, so only `economics.jsonl` is appended to it, never a
196
+ second `.kontourai/telemetry/...` suffix on top).
197
+ 3. Only then is the record best-effort POSTed to `<console>/records` via the shared
198
+ `console_post_json` transport core — detached, fail-open, and gated on
199
+ `FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY` and only when a console endpoint is configured.
200
+ 4. Every failure path is `exit 0`. The emitter only writes/relays a fact — it never mutates
201
+ a kit, gate, or claim (render-don't-execute).
202
+ 5. **Unattributed/no-signal suppression** (economics-relay-unattributed-suppression): the local
203
+ write in step 2 is unconditional, but the console POST in step 3 is additionally suppressed —
204
+ the run's fact still lands in the local `economics.jsonl`, it just never reaches the console —
205
+ when the assembled record carries **none** of the following:
206
+
207
+ ```
208
+ suppress_relay =
209
+ (task_slug is null/empty/"unattributed")
210
+ AND (cost.estimated_cost_usd == 0)
211
+ AND (no token volume — cost.input_tokens + cost.output_tokens
212
+ + cost.cache_creation_input_tokens + cost.cache_read_input_tokens == 0)
213
+ AND (defects.gate_fires == 0)
214
+ AND (defects.caught_false_completions == 0)
215
+ AND (sum of defects.findings_by_severity.* == 0)
216
+ ```
217
+
218
+ Any one of real task attribution, real cost, real token volume, or a real defect/gate signal
219
+ is enough to still relay the record unchanged. The token-volume leg exists because cost
220
+ legitimately degrades to `0` on an unpriced/new model (see `scripts/telemetry/lib/usage.sh`'s
221
+ contract) while the transcript-ground-truth token counts remain a real signal — without this
222
+ leg, a real, unattributed, unpriced-model run with genuine token volume would be
223
+ indistinguishable from a truly-empty run and would be wrongly suppressed, dropping real ROI
224
+ data.
225
+
226
+ This guard exists because `telemetry.sh` invokes the emitter on every `session.usage` Stop
227
+ event once usage tracking is on, regardless of whether the run had an active Builder task —
228
+ with no `active_slug`, `task_slug` resolves to `null` (assembled as `state.task_slug // null`;
229
+ it is never the literal string `"unattributed"` as *data* — that literal is only how the
230
+ console *renders* a `null`/empty `task_slug` in its ROI view) and every `defects.*` field sits
231
+ at its zero default, so without this guard every no-task, no-signal run relayed a `null`
232
+ task_slug, `$0`-cost, zero-token, zero-defect record that diluted the console
233
+ `/api/economics` ROI view's `firstPassRate` and cost aggregates. The `!= "unattributed"` leg
234
+ in the predicate is harmless defense-in-depth (in case an upstream caller ever passes that
235
+ literal string), not a claim that the emitter itself produces it. `delegations[]`/`signals.*`
236
+ are deliberately excluded from the predicate — `--agents-dir` is only ever passed alongside
237
+ `--state`, so `delegations` is already `[]` whenever the other terms are at their zero
238
+ defaults.
239
+
240
+ **The guard itself fails OPEN toward relaying.** The suppress path only fires when jq
241
+ successfully evaluates the predicate above to an explicit `false` (a genuinely-empty record);
242
+ a jq/read failure (non-zero exit, or any output other than the literal `false`) falls through
243
+ and the record RELAYS unchanged — dropping a real record is worse than an extra empty one
244
+ reaching the console, so a guard failure never silently swallows real data. Set
245
+ `TELEMETRY_ECONOMICS_DEBUG=1` to log a one-line `economics-record: suppressing console relay
246
+ (...)` diagnostic to stderr whenever the guard actually suppresses a POST.
247
+
248
+ ### Enabling the relay (config-driven, opt-out — #469)
249
+
250
+ The console relay is **on by default once a Console telemetry sink is configured** — it is no
251
+ longer env-var-only. `scripts/telemetry/lib/config.sh` resolves
252
+ `FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY` as follows:
253
+
254
+ - An explicit `console_economics_relay` key in a trusted conf (`.kontourai/telemetry-console.conf`
255
+ or `~/.flow-agents/telemetry-console.conf` — mode `600`, owned by the current user; see the
256
+ telemetry-mirror trust gate in `docs/agent-usage-feedback-loop.md`) always wins: truthy
257
+ (`1`/`true`/`yes`/`on`) forces the relay on, falsey (`0`/`false`/`no`/`off`) forces it off.
258
+ - Otherwise, once `console_telemetry_url` / `console_telemetry_endpoint_url` resolves (from that
259
+ same trusted conf, or a directly-set env var), the relay **defaults on**.
260
+ - A caller-pre-set `FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY` environment variable is left untouched
261
+ and takes priority over the default-on rule (but not over an explicit conf key).
262
+ - The optional `console_economics_endpoint_url` conf key (or the
263
+ `FLOW_AGENTS_CONSOLE_ECONOMICS_ENDPOINT_URL` env var) overrides the derived `<console>/records`
264
+ endpoint when it must differ from the telemetry sink's origin.
265
+ - To opt out without hand-editing the conf, pass `--no-economics-relay` to
266
+ `install-console-config.sh`, which writes `console_economics_relay=0`.
267
+
268
+ The raw `FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY` / `FLOW_AGENTS_CONSOLE_ECONOMICS_ENDPOINT_URL` env
269
+ vars are still honored directly, for one-off invocations or CI where writing a conf file isn't
270
+ worth it — but the conf keys above are the normal path for an installed setup.
@@ -0,0 +1,74 @@
1
+ # Harness-capability matrix
2
+
3
+ **The signals a feature can rely on depend on which runtime/harness the kit is hooked into. State the
4
+ coverage; never assume it, never fabricate the gap.**
5
+
6
+ This is a cross-cutting contract: any feature whose data comes from telemetry (economics, delegation
7
+ efficiency, liveness, learning) must declare which signals it needs, which *class* each falls in, and
8
+ how it degrades when the current runtime does not expose one. A missing signal is rendered as
9
+ **unavailable**, distinct from a real zero — so a consumer never reads "harness-blind" as "measured 0."
10
+
11
+ ## The load-bearing distinction: orchestrator-observable vs sub-agent-internal
12
+
13
+ Not all "we can't see inside the sub-agent" gaps are equal. Two classes:
14
+
15
+ - **Orchestrator-observable** — what our hooks see the *main agent* (the orchestrator) do. The
16
+ orchestrator knows the prompt it dispatched, how many times it re-dispatched/re-prompted the same
17
+ sub-agent, its own iteration/route-back count, and when it superseded or corrected a result. Our
18
+ telemetry observes the orchestrator directly, so **these hold on any runtime that runs our hooks —
19
+ no sub-agent introspection required.** This is where per-delegation *outcome* comes from.
20
+ - **Sub-agent-internal** — signals that live inside the sub-agent's own turn: its token usage, its
21
+ internal reasoning, a verdict it computed but never surfaced to the orchestrator. Capturing these
22
+ requires the harness to expose per-sub-agent instrumentation. **Most harnesses do not today**, so
23
+ features that need them (e.g. per-delegation *cost*) must declare the signal unavailable and fall
24
+ back to a coarser, honest proxy.
25
+
26
+ The practical upshot for #415: we can attribute **outcome** (accepted / rework / diverged / failed) from
27
+ orchestrator-observable behavior even though we cannot attribute **cost** per delegation — because
28
+ tokens are sub-agent-internal and outcome is not.
29
+
30
+ ## The `signals` block
31
+
32
+ Every `kontour.console.economics` record carries a `signals` object declaring what the emitting runtime
33
+ exposed:
34
+
35
+ | Field | Class | Meaning |
36
+ | --- | --- | --- |
37
+ | `runtime` | — | the runtime that produced the record (`claude-code`, `kiro-cli`, `codex`, …) |
38
+ | `per_delegation_tokens` | sub-agent-internal | `true` iff the runtime isolates per-sub-agent token usage. **`false` on every runtime today** → per-delegation cost is unavailable; the console attributes cost at `(role, model)` granularity via `cost.by_model` instead. |
39
+ | `per_delegation_outcome` | orchestrator-observable | coverage of the outcome signal on this run: `full` (every delegation resolved to a real outcome), `partial` (some), `none` (delegations exist but none had a verdict/escalation/re-dispatch), `n/a` (no delegations observed). |
40
+
41
+ Consumers (console panels, `learning-review`) MUST read `signals` before rendering a metric: if the
42
+ signal a metric needs is unavailable, show "not measurable on this harness," not a misleading number.
43
+
44
+ ## Per-runtime coverage (current, honest snapshot)
45
+
46
+ `✓` exposed · `partial` best-effort/orchestrator-derived · `✗` not exposed. Update this table as
47
+ runtimes add instrumentation — it is the single source of truth the `signals` values are stamped from.
48
+
49
+ | Signal | Class | claude-code | kiro-cli | codex | raw-model runner |
50
+ | --- | --- | --- | --- | --- | --- |
51
+ | whole-run tokens/cost (`by_model`) | run-level | ✓ | ✓ | ✓ | partial |
52
+ | delegation dispatch (role/model) | orchestrator-observable | ✓ | ✓ | partial | ✗ |
53
+ | re-dispatch / re-prompt count | orchestrator-observable | ✓ | ✓ | partial | ✗ |
54
+ | escalation (tier bump) | orchestrator-observable | ✓ | ✓ | partial | ✗ |
55
+ | supersession / correction | orchestrator-observable | partial | partial | ✗ | ✗ |
56
+ | per-sub-agent terminal verdict | mixed | partial | partial | ✗ | ✗ |
57
+ | per-sub-agent tokens/cost | sub-agent-internal | ✗ | ✗ | ✗ | ✗ |
58
+
59
+ "partial" for verdicts/supersession reflects that these depend on the workflow actually recording an
60
+ `evidence`/`verdict`/`supersession` event for the agent — capturable, but not guaranteed every run.
61
+ That is exactly why `outcome` degrades to `unavailable` rather than `accepted` when the event is absent.
62
+
63
+ ## The doctrine (applies to any telemetry-dependent feature)
64
+
65
+ 1. **Classify each signal** as run-level, orchestrator-observable, or sub-agent-internal. Prefer
66
+ orchestrator-observable derivations — they are the most portable across harnesses.
67
+ 2. **Declare coverage in-band** (a `signals`-style block) so a consumer can distinguish real-zero from
68
+ harness-blind at read time.
69
+ 3. **Degrade to an explicit `unavailable`**, never to a fabricated value. A cost you cannot attribute
70
+ is not `$0`; an outcome you cannot observe is not `accepted`.
71
+ 4. **Fall back to the coarsest honest proxy** when the fine signal is missing (per-delegation cost →
72
+ `(role, model)` via `by_model`), and label the proxy as such.
73
+ 5. **Keep this matrix current.** When a harness starts exposing a signal, flip the cell and the
74
+ `signals` stamp follows — the feature "lights up" without a schema change.