@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
@@ -66,6 +66,173 @@ Phase 1 delivers the "see everyone's active sessions, and your own usage in team
66
66
  project/team insight views alongside individual ones — while coordination authority stays exactly
67
67
  where it is today (local + GitHub).
68
68
 
69
+ #### Liveness relay — flow-agents side (shipped)
70
+
71
+ The flow-agents EMIT half of the relay ships as an **optional, off-by-default** sink
72
+ (`scripts/liveness/relay.sh`). When enabled, each liveness event (`claim`/`heartbeat`/`release`) is
73
+ mirrored to the Console as a `kontour.console.liveness` record over the **same transport core** the
74
+ telemetry mirror uses — `console_post_json` in `scripts/telemetry/lib/transport.sh` (endpoint-allow
75
+ gate, `Authorization: Bearer` + `x-console-tenant-id`, timeouts, detached fire), shared not forked.
76
+
77
+ It is **strictly local-first** (ADR 0012 §5): the relay fires *after* the durable local
78
+ `liveness/events.jsonl` write, fully detached and best-effort, so it can never block, slow, or fail
79
+ the local emit — and with the flag off or no console configured it is a true no-op. Enable it with:
80
+
81
+ ```
82
+ FLOW_AGENTS_CONSOLE_LIVENESS_RELAY=1
83
+ FLOW_AGENTS_CONSOLE_LIVENESS_ENDPOINT_URL=<console>/records # or FLOW_AGENTS_CONSOLE_URL / CONSOLE_TELEMETRY_URL + /records
84
+ CONSOLE_TELEMETRY_TOKEN=<bearer> (or FLOW_AGENTS_CONSOLE_TOKEN_FILE) · CONSOLE_TENANT_ID / FLOW_AGENTS_CONSOLE_TENANT
85
+ ```
86
+
87
+ Untrusted fields (actor, subjectId, branch, artifact_dir) are JSON-escaped by `jq` at record
88
+ construction, so hostile control bytes are `\u`-escaped, never emitted raw.
89
+
90
+ #### Economics record — flow-agents side (shipped, #349)
91
+
92
+ Every kit-driven run emits **one per-run economics record** — a `kontour.console.economics` v0.1 fact
93
+ carrying `cost`, `time`, `iterations`, and `defects` caught — so "flow kits save money and produce
94
+ more accurate results" becomes a **measurable, falsifiable** claim. It is the measurement substrate
95
+ for the Kit-economics telemetry initiative and is consumed by the baseline harness (#350), the
96
+ small-model headline (#409), and the console value view (console #117). The full field-by-field
97
+ contract is in [`docs/specs/economics-record-contract.md`](../specs/economics-record-contract.md).
98
+
99
+ The emitter (`scripts/telemetry/economics-record.sh`) is modeled byte-for-byte on the liveness relay:
100
+ it assembles the record with a **single `jq -c` filter** (valid JSON + `\u`-escaping of every untrusted
101
+ field — `task_slug`, model names, finding text) and hands off to the **same** `console_post_json`
102
+ transport core (endpoint-allow gate, `Bearer` + `x-console-tenant-id`, detached fire), shared not
103
+ forked. It is wired into the telemetry **stop path** (`add_stop_data_and_emit_usage` in
104
+ `scripts/telemetry/telemetry.sh`): right after the `session.usage` event is emitted, the economics
105
+ record is assembled from that event's `.usage` block (token/cost **ground truth** parsed by
106
+ `usage_parse_transcript` from the transcript — never re-estimated) joined with the run's review
107
+ sidecars (`critique.json` for defects, `state.json` for verdict / phase / iterations).
108
+
109
+ It is **strictly local-first** (ADR 0003 call 6): the record is **always** written to the local
110
+ economics log (`<TELEMETRY_DATA_DIR>/economics.jsonl`) *first*; only then is a **detached, opt-in,
111
+ best-effort** POST fired — a true no-op when the relay flag is off or no console is configured, and
112
+ `exit 0` on every failure path so it can never block, slow, or fail a run. flow-agents emits a per-run
113
+ **fact**, never a rollup — economics aggregation and the value view are console-side **projections**
114
+ over the immutable record stream (ADR 0003 call 3). Tenancy is stamped console-side from the verified
115
+ principal (call 2); the record's `tenant_id` is self-description only. Structurally, `cost` and
116
+ `defects` are **co-required** in the schema (the R7 Goodhart guard) — no consumer can render "cheaper"
117
+ without also rendering "and here is what it caught / missed." Enable the relay with:
118
+
119
+ ```
120
+ FLOW_AGENTS_CONSOLE_ECONOMICS_RELAY=1
121
+ FLOW_AGENTS_CONSOLE_ECONOMICS_ENDPOINT_URL=<console>/records # or FLOW_AGENTS_CONSOLE_URL / CONSOLE_TELEMETRY_URL + /records
122
+ CONSOLE_TELEMETRY_TOKEN=<bearer> (or FLOW_AGENTS_CONSOLE_TOKEN_FILE) · CONSOLE_TENANT_ID / FLOW_AGENTS_CONSOLE_TENANT
123
+ ```
124
+
125
+ #### Economics relay config (default-on, opt-out — #469)
126
+
127
+ Like the telemetry mirror (see the "Owner machine mirror" section of
128
+ [`docs/agent-usage-feedback-loop.md`](../agent-usage-feedback-loop.md)), the economics relay is
129
+ now **config-driven** rather than requiring the env vars above to be exported by hand: once a
130
+ Console telemetry sink is configured (`console_telemetry_url` / `console_telemetry_endpoint_url`
131
+ in a trusted conf), the relay turns on **automatically**. It rides the exact same trusted-conf
132
+ gate as the telemetry mirror — `.kontourai/telemetry-console.conf` or
133
+ `~/.flow-agents/telemetry-console.conf`, mode `600` and owned by the current user — and the same
134
+ `https://`/`localhost` transport allowlist (`console_telemetry_endpoint_allowed` in
135
+ `scripts/telemetry/lib/transport.sh`). The derived endpoint is `<console-origin>/records` — the
136
+ same shared kind-routed ingress the liveness relay uses, and **distinct** from the telemetry
137
+ mirror's `/api/telemetry/records` path. An explicit `console_economics_relay=0` conf key, or
138
+ `install-console-config.sh --no-economics-relay`, opts back out; `console_economics_endpoint_url`
139
+ overrides the derived endpoint when needed. Full resolution order is documented in
140
+ [`docs/specs/economics-record-contract.md`](../specs/economics-record-contract.md#enabling-the-relay-config-driven-opt-out--469).
141
+
142
+ On the console side, `POST /records` already accepts the `kontour.console.economics` kind into a
143
+ per-tenant `EconomicsStore`, read back via `GET /api/economics` (console #117) — deployed.
144
+
145
+ #### Attribution & pricing correctness (#487)
146
+
147
+ Two robustness fixes close the "why is my usage landing `unattributed` at `$0`?" gap that made the
148
+ first hosted-console economics rows misleading:
149
+
150
+ - **Attribution source.** The stop path stamps the record's `task_slug` and `phase` from the
151
+ **canonical** active-session file (`.kontourai/flow-agents/current.json`), falling back to the
152
+ legacy `.flow-agents/` path only when the canonical one is absent — so a run inside a live Builder
153
+ session lands attributed to the *real* slug/phase, not a stale placeholder. A run with **no** active
154
+ session is still recorded, but as `unattributed` by design: there is no session to attribute it to,
155
+ so that is a correct null, not a bug.
156
+ - **Pricing floor.** Cost is resolved from the **bundled** pricing registry
157
+ (`scripts/telemetry/pricing.json`) as the default source. `pricing.sh` validates that any fetched or
158
+ cached body is JSON carrying `current_version` + `versions` before trusting it, and never caches a
159
+ non-JSON body — so a poisoned cache (e.g. a console gate's HTML) or a `401` from an authed pricing
160
+ endpoint **falls through to the bundled registry** instead of degrading cost to `$0`. `config.sh` no
161
+ longer silently derives an authed console pricing URL that 401s unauthenticated; the default path
162
+ uses the valid bundled registry. Net effect: a model the bundled registry can price is **never**
163
+ recorded at `$0` because of a stale/hostile cache or an unreachable remote.
164
+
165
+ Both fixes are forward-looking — they correct what future runs emit. Historical `unattributed`/`$0`
166
+ rows already ingested are not retroactively rewritten; that is a console-side data operation.
167
+
168
+ The paired **console side** — ingesting `kontour.console.liveness`, the fleet OperatingState
169
+ projection (actors + held/reclaimable subjects + last-seen + per-session cost), and the ADR 0021 §4
170
+ janitor — is tracked in the console repo (**console #125**), and feeds the redesigned console
171
+ **Fleet** panel (its per-actor `coordinationState` is already wired to render the pills). Two-machine
172
+ visibility and the janitor sweep are console-side acceptance; this repo ships only the emit half.
173
+
174
+ #### Multi-tenant install runbook (adding tenant #2 to an existing install)
175
+
176
+ Installing a second tenant against the same Console instance is a **one-command** operation, with
177
+ either installer entry point:
178
+
179
+ ```
180
+ npx @kontourai/flow-agents init --telemetry-sink kontour-hosted-console --console-tenant <tenant-id> --console-token-file <path>
181
+ ```
182
+
183
+ ```
184
+ bash install.sh <workspace> --telemetry-sink kontour-hosted-console --console-tenant <tenant-id> --console-token-file <path>
185
+ ```
186
+
187
+ Both forms drive the same mechanism. `install.sh` collects the Console-related flags into
188
+ `CONSOLE_CONFIG_ARGS` (`install.sh:21,24-27`) and passes them straight through to the installed
189
+ `scripts/telemetry/install-console-config.sh` once the bundle is copied into place (`install.sh:54-55`).
190
+ `flow-agents init`'s `installBundle()` shells out to that same freshly-installed `install.sh` with the
191
+ equivalent flags (`src/cli/init.ts:385-408`); when a raw token value (rather than a file) is supplied,
192
+ `installBundle()` first writes it to a private `mkdtemp` file with `mode: 0o600` before passing
193
+ `--console-token-file` through (`src/cli/init.ts:390-398`), so a bearer token never sits in argv or an
194
+ unprotected file even transiently.
195
+
196
+ Interactive `flow-agents init` (run without `--yes`/`--headless`) offers a third way to reach this same
197
+ mechanism: a guided "Connect to Kontour Console? [Hosted/Local/Self-hosted/Skip]" prompt that collects
198
+ the token and tenant inline, then ends the install by auto-running the same reachability/token/tenant
199
+ check `flow-agents telemetry-doctor` performs headlessly, folded into a post-install summary. Power
200
+ users who prefer the explicit flag-driven forms above can still use them directly; scripted/CI installs
201
+ (`--yes`/`--headless`) are unaffected by the wizard.
202
+
203
+ **Token provisioning** is console-side, not flow-agents-side: see the "Ingest" bullet above — tokens
204
+ are configured through `CONSOLE_AUTH_TOKENS_JSON`. Issuing tenant #2 a token, or rotating an existing
205
+ one, is entirely console-repo scope; this repo only ever consumes a token file/value handed to it by
206
+ that process, it does not mint or manage tokens itself.
207
+
208
+ **Idempotency:** re-running the installer (e.g. to update a tenant's token after rotation, or to add
209
+ `--console-tenant` to an install that didn't originally have one) is safe to repeat. `set_config_key`
210
+ in `install-console-config.sh` (lines 88-100) rewrites `telemetry.conf` by awk-filtering out any
211
+ existing `key=` line for that key and appending the new value, so re-running with the same or updated
212
+ flags updates the same conf keys in place rather than duplicating or appending stale entries.
213
+
214
+ **Post-install verification:** run
215
+
216
+ ```bash
217
+ flow-agents telemetry-doctor --dest <workspace> --json --headless
218
+ ```
219
+
220
+ and check the `console` block of the report: `tokenConfigured` and `tenantConfigured`
221
+ (`src/cli/telemetry-doctor.ts:208-209`) should both be `true`, and `reachability`
222
+ (`src/cli/telemetry-doctor.ts:210`) should report `ok: true` for a healthy install. A misconfigured
223
+ tenant install typically shows `tenantConfigured: false` (no `--console-tenant` at install time) or
224
+ `reachability.ok: false` (endpoint unreachable, or the token was rejected).
225
+
226
+ **Isolation proof:** `evals/integration/test_console_tenant_isolation.sh` installs two distinct
227
+ tenants side by side through this exact `install-console-config.sh` → `config.sh` → `transport.sh`
228
+ path against a local HTTP stub, and asserts each tenant's outbound POST carries only its own
229
+ `x-console-tenant-id` and bearer token, never the other's. This proves the **client-side wire
230
+ contract** — that a correctly configured install sends the right headers for the right tenant. It
231
+ does **not** prove server-side tenant isolation or enforcement (that a token cannot be used to read
232
+ another tenant's data); that remains Console-repo scope (console #98–#100 tenant hardening, see
233
+ *Phase 2* below).
234
+
235
+
69
236
  ### Phase 2 — authoritative fleet coordination
70
237
 
71
238
  Opt-in, for teams that want cross-machine assignment arbitration:
@@ -54,7 +54,7 @@ Required fields:
54
54
  | `name` | Non-empty display name |
55
55
  | `flows` | Non-empty list; each entry must have `id` and `path` |
56
56
 
57
- Optional fields: `product_name`, `description`, `skills`, `docs`, `adapters`, `evals`, `assets`, `dependencies` (cross-kit dependency declarations; see [Cross-kit dependencies](#cross-kit-dependencies)). Optional fields list relative asset paths or objects with `id`, `path`, and optional `description`. `skills` and `docs` assets are activated by both adapters alongside flows. `adapters`, `evals`, and `assets` appear in diagnostics as `skipped_assets` (see the Activate section for the full per-adapter table).
57
+ Optional fields: `product_name`, `description`, `skills`, `docs`, `adapters`, `evals`, `assets`, `dependencies` (cross-kit dependency declarations; see [Cross-kit dependencies](#cross-kit-dependencies)), `workflow_triggers` (structured workflow steering keyed by engine trigger names such as `implementation-work-detected`), `hook_influence_expectations` (kit-owned required hook-influence fixture expectations), and `first_party` (legacy catalog/marketplace metadata; no runtime privilege). Optional asset fields list relative asset paths or objects with `id`, `path`, and optional `description`. `skills` and `docs` assets are activated by both adapters alongside flows. `adapters`, `evals`, and `assets` appear in diagnostics as `skipped_assets` (see the Activate section for the full per-adapter table).
58
58
 
59
59
  ## Minimal flow file
60
60
 
@@ -181,6 +181,14 @@ When installing through `npx @kontourai/flow-agents init` with the Codex runtime
181
181
  npx @kontourai/flow-agents init --runtime codex --dest /path/to/workspace --activate-kits --yes
182
182
  ```
183
183
 
184
+ `init` activates no kits by default. To activate one or more catalog kits explicitly, pass `--activate-kit <kit-id>` once per selected kit. Declared kit dependencies are auto-included transitively during activation; for example, activating Builder also activates its Knowledge dependency:
185
+
186
+ ```bash
187
+ npx @kontourai/flow-agents init --runtime codex --dest /path/to/workspace --activate-kit builder --yes
188
+ ```
189
+
190
+ `--activate-kits` remains a backwards-compatible alias for selecting every catalog kit.
191
+
184
192
  ## Troubleshooting
185
193
 
186
194
  Common validation errors and fixes are documented in the [Flow Kit Repository Contract](flow-kit-repository-contract.md#common-failures). The most frequent:
@@ -271,6 +279,42 @@ Where it is checked:
271
279
 
272
280
  `dependencies` is metadata, not evaluable-gate content, so it does **not** affect K0/K1/K2 conformance scoring (see the K-levels section below).
273
281
 
282
+ ### Structured workflow triggers
283
+
284
+ A kit may declare `workflow_triggers` to let the engine route matching prompts into a kit workflow. Triggers do not contain freeform prose. The engine owns the rendered wording and applies the same template to every kit. Every trigger steering field is an identifier and must match `^[a-z0-9]+(?:[.-][a-z0-9]+)*$`; malformed values fail closed and the trigger is skipped.
285
+
286
+ Shape:
287
+
288
+ ```json
289
+ {
290
+ "workflow_triggers": [
291
+ {
292
+ "id": "builder-build-work",
293
+ "when": "implementation-work-detected",
294
+ "target_flow_id": "builder.build",
295
+ "default_skill": "deliver",
296
+ "conditional_skills": [
297
+ { "when": "user-requested-tdd", "skill": "tdd-workflow" }
298
+ ],
299
+ "required_sequence": ["plan-work", "execute-plan", "review-work", "verify-work"],
300
+ "post_verify_targets": ["release-readiness", "learning-review"]
301
+ }
302
+ ]
303
+ }
304
+ ```
305
+
306
+ | Field | Rule |
307
+ |---|---|
308
+ | `id` | Required trigger id unique within the kit. |
309
+ | `when` | Required engine trigger category such as `implementation-work-detected` or `knowledge-capture-detected`. |
310
+ | `target_flow_id` | Optional flow id the session should stay on. |
311
+ | `default_skill` | Optional skill id to activate when no conditional skill applies. |
312
+ | `conditional_skills` | Optional list of `{ "when": "...", "skill": "..." }` identifier entries. |
313
+ | `required_sequence` | Optional ordered list of workflow skills or steps the engine should name as the required sequence. |
314
+ | `post_verify_targets` | Optional list of post-verification targets such as release readiness or learning capture. |
315
+
316
+ The retired `hint` field is invalid. `display_name` is not a workflow trigger field; keep human-readable names in catalog/listing metadata such as top-level `name` or `product_name`. Do not put natural-language steering instructions in kit metadata; express routing needs through the structured identifier fields above.
317
+
274
318
  ### When a kit "is" a Flow Agents Kit
275
319
 
276
320
  A kit is a **Flow Agents Kit** when it satisfies both layers:
@@ -378,36 +422,23 @@ Every kit carries two independent badges:
378
422
  | Axis | Values | Question answered |
379
423
  |---|---|---|
380
424
  | **Capability** (K-level) | K0 / K1 / K2 | What does the kit CONTAIN? (derived from assets) |
381
- | **Trust** | first-party / verified / unverified | WHO vouches for it? (derived from provenance) |
425
+ | **Trust** | verified / unverified | WHO vouches for it? |
382
426
 
383
- A K2 kit can be `unverified`. A K0 kit can be `first-party`. The levels are independent.
427
+ A K2 kit can be `unverified`. The levels are independent.
384
428
 
385
- **Marketplace listing format**: `Works with: Flow (gates-only) | K1 | ✓ First-party`
429
+ **Marketplace listing format**: `Works with: Flow (gates-only) | K1 | Official`
430
+
431
+ `official` / `first_party` is catalog and marketplace metadata only. It grants no runtime privilege: every kit uses the same structured steering mechanism, and no capability decision keys off kit id or load provenance.
386
432
 
387
433
  ### Trust levels
388
434
 
389
435
  | Level | Meaning | How it is assigned (v1) |
390
436
  |---|---|---|
391
- | `first-party` | Kontour authored, tested, and ships this kit in the `@kontourai/flow-agents` package. | Kit id is in the internal FIRST_PARTY_KIT_IDS allowlist in `src/flow-kit/validate.ts`. |
392
437
  | `verified` | Reserved for a future third-party verification process. | Not yet implemented; the value is reserved but not granted to any kit today. |
393
- | `unverified` | Default for all kits not explicitly vouched for. | All other kits, including third-party community kits. |
438
+ | `unverified` | Default for all kits. | All current kits, including built-ins and third-party community kits. |
394
439
 
395
440
  `unverified` says nothing about the quality of a kit — it only means Kontour has not vouched for it through one of the above channels.
396
441
 
397
- ### First-party kits (v1)
398
-
399
- The first-party allowlist in v1 contains the kits authored by Kontour and distributed with the flow-agents package:
400
-
401
- - `builder` — Builder Kit (shape, build, and deliver work)
402
- - `knowledge` — Knowledge Kit (durable gated knowledge store)
403
-
404
- Criteria for a kit to be first-party:
405
- 1. Its directory lives under `kits/` in the `kontourai/flow-agents` repository.
406
- 2. It is published as part of the `@kontourai/flow-agents` npm package.
407
- 3. Kontour owns and maintains the kit's content and release lifecycle.
408
-
409
- Third-party forks, community kits, or kits published under a different npm package are NOT first-party even if they share a similar id. First-party is tied to provenance in this specific repository and package.
410
-
411
442
  ### Deferred: verified trust and cryptographic attestation (v2)
412
443
 
413
444
  The `verified` value is reserved for a future verification process. The intended v2 path:
@@ -438,7 +469,7 @@ npm run kit -- inspect kits/builder
438
469
  },
439
470
  "targets": ["flow", "flow-agents"],
440
471
  "third_party_extensions": [],
441
- "trust": "first-party"
472
+ "trust": "unverified"
442
473
  }
443
474
  ```
444
475
 
@@ -0,0 +1,80 @@
1
+ # Builder Flow Runtime
2
+
3
+ Builder Kit build sessions use Flow as the authority for steps, gates, transitions,
4
+ route-backs, and attempt limits. Flow Agents supplies the agent-facing adapter: it
5
+ starts or loads the canonical run, attaches the session trust bundle, and projects
6
+ Flow's current state into the existing workflow sidecars.
7
+
8
+ ## Ownership Boundary
9
+
10
+ - Flow persists generated run state under `.kontourai/flow/runs/<run-id>/` and owns
11
+ Flow Definition evaluation.
12
+ - Builder Kit declares agent actions for each Flow step in `kit.json` under
13
+ `flow_step_actions`. Actions name skills or product operations; they do not alter
14
+ Flow transitions or gate outcomes.
15
+ - Flow Agents compiles its kit-level `uses_flow` extension and gate-less `done`
16
+ sentinel into one Flow-native effective definition. The generated definition is
17
+ content-addressed under `.kontourai/flow-agents/runtime-definitions/`; Flow then
18
+ owns all evaluation of that definition.
19
+ - Flow Agents writes product session artifacts under
20
+ `.kontourai/flow-agents/<slug>/`, produces Hachure trust bundles through Surface,
21
+ and projects the canonical Flow run into `state.json` and `current.json`.
22
+ - Durable Flow Definitions remain authored under `kits/builder/flows/`. Generated
23
+ state has no `.flow/runs` fallback.
24
+
25
+ The adapter contains no benchmark, task, filename, or grader-specific guidance.
26
+ It derives its next action from the persisted Flow step, the current gate's declared
27
+ expectations, and Builder Kit's structured action map.
28
+
29
+ ## Entry And Synchronization
30
+
31
+ A Builder session must first be created at the Flow Definition's entry step by the
32
+ workflow sidecar. Start the canonical run with:
33
+
34
+ ```bash
35
+ flow-agents builder-run start --session-dir .kontourai/flow-agents/<slug>
36
+ ```
37
+
38
+ `start` requires exactly one `state.work_item_refs` entry and uses that stable Work
39
+ Item reference as the Flow run subject. It is idempotent for an existing canonical
40
+ run. A direct primitive session without a Builder Flow stamp remains independent and
41
+ does not create a Flow run.
42
+
43
+ After a gate producer writes `trust.bundle`, the sidecar writer synchronizes an
44
+ existing run automatically. Interrupted clients can replay the projected command:
45
+
46
+ ```bash
47
+ flow-agents builder-run sync --session-dir .kontourai/flow-agents/<slug>
48
+ ```
49
+
50
+ Synchronization is digest-idempotent. The same trust bundle is not attached twice.
51
+ Inspection loads the run without evaluating it, so invalid evidence is rejected
52
+ before Flow mutation.
53
+
54
+ ## Trust Binding
55
+
56
+ Claims relevant to the current gate must carry
57
+ `metadata.workflow_subject_ref` equal to the persisted Flow run subject. Unrelated
58
+ claims are ignored for that gate. A relevant claim with a missing or different
59
+ subject reference is rejected; Flow is not mutated.
60
+
61
+ A failed gate claim may include a Flow classifier through
62
+ `record-gate-claim --status fail --route-reason <reason>`. Flow validates the reason
63
+ against the gate's `on_route_back` map and owns both the destination and attempt
64
+ budget. Flow Agents only projects the resulting attempt and maximum into `state.json`.
65
+
66
+ ## Agent Projection
67
+
68
+ While a run is active, `state.json` contains:
69
+
70
+ - `flow_run`: canonical run identity, current step, open gates, run reference, and
71
+ route-back attempt information when present.
72
+ - `next_action.skills`: ordered Builder skills for the current step.
73
+ - `next_action.operations`: ordered non-skill product operations when present.
74
+ - `next_action.summary`: required gate claims derived from the Flow Definition.
75
+ - `next_action.command`: the exact idempotent synchronization command.
76
+
77
+ Workflow steering surfaces these fields on session start and prompt submission. The
78
+ Stop hook treats an unfinished canonical Flow run as active even during pickup or
79
+ planning, blocks a premature stop in block mode, and does not release its liveness
80
+ claim. A run is complete only when Flow reaches its terminal step.
@@ -45,6 +45,46 @@ Telemetry channels redact sensitive payload fields before emission. Adapters mus
45
45
 
46
46
  These defaults are configurable via `TELEMETRY_CHANNEL_FULL_REDACT` and `TELEMETRY_CHANNEL_ANALYTICS_REDACT` environment variables.
47
47
 
48
+ ### Attribution Fields (canonical, all adapters)
49
+
50
+ Because `context.cwd` is redacted on the analytics/console relay (the full local path must never
51
+ leave the machine), adapters cannot rely on the consumer deriving a project name from it. So the
52
+ attribution label is itself canonical and every adapter — harness **and** framework — must produce it:
53
+
54
+ | Field | Semantics | Redacted? | Producer requirement |
55
+ | --- | --- | --- | --- |
56
+ | `context.project` | Coarse, path-free project label identifying the project a session is working in. | **No** (path-free by construction; safe to relay) | Adapters SHOULD populate `context.project` before emission when `context.project` is not already set, using the **canonical derivation precedence** below. Never send the full path. |
57
+
58
+ **Canonical derivation precedence (all adapters MUST resolve in this order, first match wins).** A
59
+ bare `basename(cwd)` is *not* sufficient — folder names differ between developers, clones, and
60
+ worktrees, so the same project would report inconsistent labels. Resolve most-stable-first:
61
+
62
+ 1. `FLOW_AGENTS_PROJECT` — explicit operator override; always wins.
63
+ 2. **Nearest project manifest name** walking up from the working dir — `package.json` `name` today
64
+ (monorepo-granular *and* committed, so every developer of the same package resolves the same
65
+ label). Other ecosystems (`pyproject.toml`, `Cargo.toml`, `go.mod`, …) extend this step — see
66
+ the consistency callout.
67
+ 3. **Git remote `org/repo`** (from `remote.origin.url`, `.git` stripped, path-free) — repo-level
68
+ identity that is stable across clones and worktrees.
69
+ 4. **Git toplevel directory basename** — the repo dir even when invoked from a worktree or subdir.
70
+ 5. **`basename(cwd)`** — last resort.
71
+
72
+ Derivation is deterministic for a given working tree, so adapters SHOULD compute it once per
73
+ project per session and cache it rather than re-resolving per event.
74
+
75
+ Consumers (the console projection) prefer `context.project` and fall back to `basename(context.cwd)`
76
+ only when a non-redacted cwd is present. This keeps project attribution consistent across every
77
+ runtime — a Claude Code shell hook and an in-process Strands adapter derive the same label the same
78
+ way.
79
+
80
+ **Consistency callouts (features that cannot be identical across runtimes — surface, don't hide):**
81
+ - A runtime that exposes no working directory cannot resolve steps 2–5 and attributes to "unknown"
82
+ by design; that gap must be surfaced, not silently bucketed (§"Degradation when host lacks
83
+ trigger").
84
+ - Step 2's manifest matrix is inherently language-specific. Until an adapter's ecosystem manifest is
85
+ supported, it resolves from step 3 onward. Adapters MUST document which manifests they honor so the
86
+ attribution granularity difference is explicit, not surprising.
87
+
48
88
  ### Exit Code Protocol (Canonical Hook Scripts)
49
89
 
50
90
  Canonical hook scripts in `scripts/hooks/` use the following exit code contract — originally derived from Kiro conventions and shared across all harness adapters via the adapter translation layer:
@@ -198,7 +238,11 @@ Flow Agents currently ships five canonical policy classes. Each policy class has
198
238
  { "command": "npm test", "observedResult": "pass", "exitCode": 0, "capturedAt": "2026-06-23T00:00:00Z", "source": "postToolUse-capture" }
199
239
  ```
200
240
 
201
- **Exit-code handling (deterministic observation only)**: a clean integer exit code is host-dependent. The policy extracts the real exit code where the host surfaces one (`tool_response`/`tool_output` `.exitCode`/`.exit_code`/`.status`/`.code`/`.returnCode`, or top-level equivalents) and sets `observedResult` to `pass` iff that code is `0`. When no clean integer exit code is present, `exitCode` is recorded as `null` and `observedResult` is inferred *only* from deterministic failure signals — a non-empty `error`, a `success:false`/`failed:true`/`is_error:true` flag, or a non-empty stderr with no stdout. Plain stdout text is never scanned for the words "error"/"fail"; the model's narration is never consulted.
241
+ **Exit-code handling (deterministic observation only)**: a clean integer exit code is host-dependent. The policy extracts the real exit code where the host surfaces one (`tool_response`/`tool_output` `.exitCode`/`.exit_code`/`.status`/`.code`/`.returnCode`, or top-level equivalents) and sets `observedResult` to `pass` iff that code is `0`. When no clean integer exit code is present, `exitCode` is recorded as `null` and `observedResult` is inferred *only* from deterministic failure signals — a non-empty `error`, a `success:false`/`failed:true`/`is_error:true` flag, or a non-empty stderr with no stdout. When no clean integer exit code is present AND no deterministic failure signal exists, `exitCode` is recorded as `null` and `observedResult` is **`ambiguous`** — never `pass`. A `pass` always requires positive evidence (a clean integer exit code of 0; no host currently surfaces a positive success flag). Plain stdout text is never scanned for the words "error"/"fail"; the model's narration is never consulted.
242
+
243
+ **Consumer semantics of `ambiguous`**: `ambiguous` is non-confirming everywhere it is consumed — it never confirms a claimed pass and never itself resolves to a `verified` event, regardless of which runtime or extraction path produced it. `workflow-sidecar`'s trust-bundle build (`reduceCaptureLogByCommand` / `captureByCommand`) maps a command-log `ambiguous` capture to the existing canonical `not_verified` status — the same status record-check already uses for an ambiguous grep/diff exit — so the evidence item is `passing:false` and no `verified` event is ever emitted for it. `stop-goal-fit` (§2.3) further distinguishes the ORIGIN of an ambiguous capture: the absence-ambiguous case (a bare `grep`/`diff` that exited exactly `1`, which re-running yields no new information for) keeps its grep/diff-flavored `NOT_VERIFIED (ambiguous)` message and HARD_BLOCKs a terminal stop; a generic no-signal-ambiguous capture — no exit code observed at all, e.g. an unreadable Codex rollout banner or a bounded read that exceeded its caps — gets a grep/diff-free `NOT_VERIFIED —` message that is warn-only at a terminal stop but still blocks a non-terminal (in-flight) stop via the same `NOT_VERIFIED —` pattern. Neither path ever demotes to a confirmed `pass`.
244
+
245
+ **Codex host-banner carve-out**: On the Codex runtime, the host serializes the real exit code as host-generated prose (`Process exited with code N`) inside the tool result / session rollout rather than as a structured field (observed on codex-cli 0.142.5). The codex ADAPTER (`scripts/hooks/codex-hook-adapter.js`, scoped to the evidence-capture invocation on `postToolUse` only) extracts exactly that banner and injects it as a structured `tool_response.exitCode` BEFORE capture observes; this is a deterministic HOST signal (host-authored fixed format), not narration scanning — capture itself still never scans stdout or model narration. Extraction (`scripts/hooks/lib/codex-exit-code.js`) is **preamble-anchored**: the banner in a `function_call_output.output` string sits in the HOST-authored preamble, before the model's own stdout, which the codex CLI appends after a literal `Output:` delimiter; extraction matches the banner only in the portion BEFORE that delimiter (the FIRST match when no delimiter is present) and never scans the post-delimiter model stdout, so a command that deliberately prints a forged `Process exited with code 0` to its own stdout cannot override the real host-reported code. Reads are **head-anchored and bounded**: the target rollout line is located via a bounded backward scan (default 1MB), and only the first ~64KB of that line is read/parsed — the preamble/banner lives within the first few hundred bytes of the `output` field regardless of how much model stdout follows, so a >64KB flood of stdout after the banner can never displace it out of the read window; beyond either bound, extraction yields no signal (`null` → the `ambiguous` default above), never a guess. Extraction **correlates or declines** rather than blindly trusting the newest rollout entry: a payload-carried call_id match is authoritative when present; absent that, the newest `function_call_output` is cross-checked against its paired `function_call`'s command, and a resolvable match uses it while a resolvable **mismatch DECLINES to no signal** (never attributes another call's exit code); only when no correlation signal exists at all (the common single-call case) does it fall back to the newest banner. The `transcript_path` is resolved through `realpath` and required to be a regular file — and, when the codex sessions root itself is resolvable, contained within it — before any read. Extraction is fail-open throughout (missing/unreadable rollout, an unresolvable line, a declined correlation, or no banner → no injection → the `ambiguous` default above, subject to the consumer semantics described above).
202
246
 
203
247
  **Decision contract**: Non-blocking. Always exits 0 and echoes stdin. Idempotent/append-only. Fail-open on any error — a capture failure must never block the agent or corrupt the log. Only records when an active workflow artifact dir resolves (otherwise there is nothing to anchor the log to).
204
248