@kontourai/flow-agents 0.1.1 → 0.2.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 (97) hide show
  1. package/.github/dependabot.yml +23 -0
  2. package/.github/workflows/publish-npm.yml +1 -1
  3. package/.github/workflows/release-please.yml +31 -0
  4. package/.github/workflows/runtime-compat.yml +118 -0
  5. package/CHANGELOG.md +38 -0
  6. package/CONTRIBUTING.md +4 -0
  7. package/README.md +58 -19
  8. package/build/src/cli/init.js +215 -5
  9. package/build/src/cli/utterance-check.js +236 -0
  10. package/build/src/cli.js +3 -0
  11. package/build/src/tools/build-universal-bundles.js +268 -0
  12. package/build/src/tools/filter-installed-packs.js +3 -0
  13. package/build/src/tools/validate-source-tree.js +6 -1
  14. package/context/scripts/telemetry/lib/config.sh +5 -1
  15. package/context/settings/flow-agents-settings.json +7 -0
  16. package/docs/agent-system-guidebook.md +4 -5
  17. package/docs/context-map.md +1 -0
  18. package/docs/index.md +46 -6
  19. package/docs/integrations/conformance.md +246 -0
  20. package/docs/integrations/framework-adapter.md +275 -0
  21. package/docs/integrations/harness-install.md +213 -0
  22. package/docs/integrations/index.md +54 -0
  23. package/docs/north-star.md +3 -3
  24. package/docs/repository-structure.md +1 -1
  25. package/docs/skills-map.md +10 -4
  26. package/docs/spec/runtime-hook-surface.md +472 -0
  27. package/docs/survey-utterance-check.md +308 -0
  28. package/docs/vision.md +45 -0
  29. package/docs/workflow-usage-guide.md +1 -1
  30. package/evals/acceptance/run.sh +4 -2
  31. package/evals/acceptance/test_opencode_harness.sh +121 -0
  32. package/evals/acceptance/test_pi_harness.sh +98 -0
  33. package/evals/integration/test_bundle_install.sh +226 -1
  34. package/evals/integration/test_bundle_lifecycle.sh +641 -0
  35. package/evals/integration/test_utterance_check.sh +518 -0
  36. package/evals/run.sh +2 -0
  37. package/evals/static/test_universal_bundles.sh +137 -2
  38. package/integrations/strands/README.md +256 -0
  39. package/integrations/strands/example.py +74 -0
  40. package/integrations/strands/flow_agents_strands/__init__.py +27 -0
  41. package/integrations/strands/flow_agents_strands/hooks.py +194 -0
  42. package/integrations/strands/flow_agents_strands/policy.py +348 -0
  43. package/integrations/strands/flow_agents_strands/steering.py +172 -0
  44. package/integrations/strands/flow_agents_strands/telemetry.py +238 -0
  45. package/integrations/strands/pyproject.toml +38 -0
  46. package/integrations/strands/tests/__init__.py +0 -0
  47. package/integrations/strands/tests/test_hooks.py +304 -0
  48. package/integrations/strands/tests/test_policy.py +315 -0
  49. package/integrations/strands/tests/test_telemetry.py +184 -0
  50. package/integrations/strands-ts/README.md +224 -0
  51. package/integrations/strands-ts/bin/conformance-shim.mjs +257 -0
  52. package/integrations/strands-ts/package.json +53 -0
  53. package/integrations/strands-ts/src/hooks.ts +208 -0
  54. package/integrations/strands-ts/src/index.ts +22 -0
  55. package/integrations/strands-ts/src/policy.ts +345 -0
  56. package/integrations/strands-ts/src/telemetry.ts +251 -0
  57. package/integrations/strands-ts/test/test-policy.ts +322 -0
  58. package/integrations/strands-ts/test/test-telemetry.ts +226 -0
  59. package/integrations/strands-ts/tsconfig.json +20 -0
  60. package/package.json +7 -2
  61. package/packaging/conformance/README.md +142 -0
  62. package/packaging/conformance/fixtures/config-protection--allow-no-path.json +18 -0
  63. package/packaging/conformance/fixtures/config-protection--allow-safe-file.json +20 -0
  64. package/packaging/conformance/fixtures/config-protection--block-biome.json +20 -0
  65. package/packaging/conformance/fixtures/config-protection--block-eslintrc.json +20 -0
  66. package/packaging/conformance/fixtures/quality-gate--allow-no-path.json +17 -0
  67. package/packaging/conformance/fixtures/quality-gate--allow-nonexistent-file.json +19 -0
  68. package/packaging/conformance/fixtures/stop-goal-fit--allow-clean-cwd.json +17 -0
  69. package/packaging/conformance/fixtures/stop-goal-fit--block-strict-mode.json +23 -0
  70. package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +21 -0
  71. package/packaging/conformance/fixtures/workflow-steering--allow-no-state.json +16 -0
  72. package/packaging/conformance/fixtures/workflow-steering--inject-active-state.json +29 -0
  73. package/packaging/conformance/fixtures/workflow-steering--inject-subagent-steering.json +25 -0
  74. package/packaging/conformance/package.json +4 -0
  75. package/packaging/conformance/run-conformance.js +322 -0
  76. package/packaging/manifest.json +59 -0
  77. package/schemas/flow-agents-settings.schema.json +48 -0
  78. package/scripts/README.md +5 -0
  79. package/scripts/dogfood.js +16 -0
  80. package/scripts/hooks/opencode-hook-adapter.js +123 -0
  81. package/scripts/hooks/opencode-telemetry-hook.js +101 -0
  82. package/scripts/hooks/pi-hook-adapter.js +123 -0
  83. package/scripts/hooks/pi-telemetry-hook.js +105 -0
  84. package/scripts/hooks/run-hook.js +8 -0
  85. package/scripts/hooks/utterance-check.js +327 -0
  86. package/scripts/telemetry/lib/config.sh +5 -1
  87. package/skills/idea-to-backlog/SKILL.md +1 -1
  88. package/src/cli/init.ts +219 -6
  89. package/src/cli/utterance-check.ts +324 -0
  90. package/src/cli.ts +3 -0
  91. package/src/tools/build-universal-bundles.ts +266 -0
  92. package/src/tools/filter-installed-packs.ts +3 -0
  93. package/src/tools/validate-source-tree.ts +6 -1
  94. package/build/src/cli/docs-preview.js +0 -39
  95. package/build/src/cli/export-bookmarks.js +0 -38
  96. package/build/src/cli/import-bookmarks.js +0 -50
  97. package/build/src/cli/instinct-cli.js +0 -93
@@ -0,0 +1,472 @@
1
+ ---
2
+ title: Runtime Hook Surface Spec
3
+ ---
4
+
5
+ # Runtime Hook Surface Spec
6
+
7
+ This document is the canonical reference for adapter authors who need to surface Flow Agents canonical policies and telemetry events on a new agent runtime or framework. It is runtime-neutral: the policies compile to whatever hook surface a host exposes.
8
+
9
+ ## Audience
10
+
11
+ Adapter authors building:
12
+
13
+ - **Harness adapters** — install-time file wiring for agent runtimes (Claude Code, Codex, Kiro, opencode, pi).
14
+ - **Framework adapters** — in-process language-native packages for agent frameworks (AWS Strands, VoltAgent, LangGraph, OpenAI Agents SDK).
15
+
16
+ Readers should be familiar with the Flow Agents operating layer described in [Developer Architecture](../developer-architecture.html) and the operating layers in [Operating Layers](../operating-layers.html).
17
+
18
+ ---
19
+
20
+ ## 1. Canonical Event Taxonomy
21
+
22
+ Every Flow Agents event has a canonical name that is runtime-neutral. Adapters map these canonical names to host-native event names. The source of truth is `scripts/hooks/claude-telemetry-hook.js` and `scripts/hooks/codex-telemetry-hook.js`.
23
+
24
+ ### Canonical Telemetry Events
25
+
26
+ | Canonical Name | Trigger Semantics | Required Payload Fields | Optional Payload Fields |
27
+ | --- | --- | --- | --- |
28
+ | `agentSpawn` | A new agent session starts. Maps from `SessionStart` on Claude Code and Codex. | `hook_event_name`, `cwd` | `session_id`, `agent_id`, `model`, `runtime` |
29
+ | `userPromptSubmit` | The user submits a new turn or message. Maps from `UserPromptSubmit`. | `hook_event_name` | `turn.prompt_text` (redacted by default), `cwd` |
30
+ | `preToolUse` | Immediately before a tool call is executed. Maps from `PreToolUse`. | `hook_event_name`, `tool_name`, `tool_input` | `tool_id`, `cwd` |
31
+ | `permissionRequest` | The runtime is asking for permission to run a tool or action. Maps from `PermissionRequest`. | `hook_event_name`, `tool_name` | `tool_input`, `cwd` |
32
+ | `postToolUse` | After a tool call completes (success or failure). Maps from `PostToolUse` and `PostToolUseFailure`. | `hook_event_name`, `tool_name`, `tool_response` | `tool_input`, `tool_output`, `error`, `cwd` |
33
+ | `stop` | The agent is about to stop and return control to the user. Maps from `Stop` and `SessionEnd`. | `hook_event_name` | `stop_reason`, `cwd` |
34
+ | `subagentStart` | A subagent or specialist delegate is spawning. Maps from `SubagentStart` (Claude Code). | `hook_event_name` | `agent_name`, `agent_type` |
35
+ | `subagentStop` | A subagent or specialist delegate has stopped. Maps from `SubagentStop` (Claude Code). | `hook_event_name` | `agent_name`, `outcome` |
36
+
37
+ ### Redaction Defaults
38
+
39
+ Telemetry channels redact sensitive payload fields before emission. Adapters must honor these defaults and must not log raw hook payloads without applying channel redaction.
40
+
41
+ | Channel | Default Redacted Fields |
42
+ | --- | --- |
43
+ | `full` | `hook.raw_input`, `turn.prompt_text`, `tool.input`, `tool.output` |
44
+ | `analytics` | `tool.input`, `tool.output`, `turn.prompt_text`, `delegation.targets.query`, `context.cwd`, `hook.raw_input` |
45
+
46
+ These defaults are configurable via `TELEMETRY_CHANNEL_FULL_REDACT` and `TELEMETRY_CHANNEL_ANALYTICS_REDACT` environment variables.
47
+
48
+ ### Exit Code Protocol (Canonical Hook Scripts)
49
+
50
+ 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:
51
+
52
+ | Exit Code | Semantics |
53
+ | --- | --- |
54
+ | `0` | Allow / pass through. The policy has no objection. |
55
+ | `2` | Block. The policy is vetoing the action (applicable to `preToolUse` and strict stop hooks). |
56
+ | other | Error. Adapters must treat errors as fail-open (allow). |
57
+
58
+ Adapters translate these exit codes into the host-native response format. The `claude-hook-adapter.js` and `codex-hook-adapter.js` wrappers perform this translation, and all errors fail open so hook runtime failures never block agent work.
59
+
60
+ ---
61
+
62
+ ## 2. Policy Classes
63
+
64
+ Flow Agents currently ships four canonical policy classes. Each policy class has a canonical hook script under `scripts/hooks/` and may be wired to one or more canonical trigger events.
65
+
66
+ ### 2.1 Workflow Steering
67
+
68
+ **Intent**: Inject phase-transition reminders and ambient workflow-state guidance so the agent does not lose track of where it is in the delivery pipeline after subagent calls or context compaction.
69
+
70
+ **Canonical script**: `scripts/hooks/workflow-steering.js`
71
+
72
+ **Canonical trigger event**: `userPromptSubmit` (ambient state guidance), `postToolUse` (after `InvokeSubagents` tool calls)
73
+
74
+ **Inputs consumed**:
75
+ - `.flow-agents/<slug>/state.json` — current workflow phase and status
76
+ - `.flow-agents/<slug>/critique.json` — open critique findings
77
+ - `docs/context-map.md` — structure hint for repo navigation
78
+
79
+ **Decision contract**: Non-blocking. Always exits 0. Appends steering text to the agent's context via `additionalContext` in the hook response. Does not block any action.
80
+
81
+ **Degradation when host lacks trigger**: If the host has no `userPromptSubmit`-equivalent hook, workflow steering is silent. The agent receives no ambient phase reminders at turn start. This is a capability loss, not a blocking failure. Log the gap in the adapter's conformance declaration as `userPromptSubmit: no native equivalent — steering context injection unavailable`.
82
+
83
+ **Codex live hook influence caveat**: Codex hook influence on live sessions is limited — the agent may not honor all injected context. The hook influence behavioral cases in `evals/fixtures/hook-influence/cases.json` document which behaviors are expected (`agent_must_do`) versus which may only be soft guidance. Adapters on similar runtimes should apply the same classification.
84
+
85
+ **opencode no-prompt-submit-hook caveat**: opencode does not expose a `prompt.submit`-equivalent event (see mapping table in section 5). Workflow steering context injection is unavailable for opencode adapters at the `userPromptSubmit` trigger point. Adapters may approximate this by injecting steering context into `session.created` or `session.idle`, but these are lower-fidelity substitutes because they do not fire at each user turn.
86
+
87
+ ### 2.2 Quality Gate
88
+
89
+ **Intent**: Run per-file format and lint checks immediately after edit tool calls to catch style regressions while the file is still in scope.
90
+
91
+ **Canonical script**: `scripts/hooks/quality-gate.js`
92
+
93
+ **Canonical trigger event**: `postToolUse` (after write/edit tool calls)
94
+
95
+ **Inputs consumed**:
96
+ - `tool_input.path` or `tool_input.file_path` — the modified file path
97
+ - `SA_QUALITY_GATE_FIX` env var — whether to auto-fix (`true`) or only check
98
+ - `SA_QUALITY_GATE_STRICT` env var — whether to emit warnings (`true`)
99
+ - Formatter detection (`biome.json`, `.prettierrc`, `ruff.toml`, etc.) from the file's project root
100
+
101
+ **Decision contract**: Non-blocking. Always exits 0. Logs warnings to stderr. Does not block any tool call. Only runs when the modified file extension matches a supported formatter (`.ts`, `.tsx`, `.js`, `.jsx`, `.json`, `.md`, `.go`, `.py`).
102
+
103
+ **Degradation when host lacks trigger**: If the host has no `postToolUse` hook, quality gate checks do not fire. The agent must rely on batch checks at stop time or explicit lint commands. Log the gap as `postToolUse: no native equivalent — per-file quality gate unavailable`.
104
+
105
+ ### 2.3 Stop-Goal-Fit
106
+
107
+ **Intent**: Warn (and optionally block) when the agent is about to stop but the active workflow artifact shows unresolved state: missing Definition Of Done, unchecked Goal Fit items, open evidence gaps, or failing sidecar validation.
108
+
109
+ **Canonical script**: `scripts/hooks/stop-goal-fit.js`
110
+
111
+ **Canonical trigger event**: `stop`
112
+
113
+ **Inputs consumed**:
114
+ - `.flow-agents/<slug>/*.md` — workflow artifact files (scanned for active status, DOD, Goal Fit Gate sections)
115
+ - `.flow-agents/<slug>/state.json` — workflow phase and next action
116
+ - `.flow-agents/<slug>/evidence.json` — verification verdict and NOT_VERIFIED gaps
117
+ - `.flow-agents/<slug>/critique.json` — critique status and open findings
118
+ - `FLOW_AGENTS_GOAL_FIT_STRICT` env var — `true` to make blocking (exit 2) instead of warning-only (exit 0)
119
+
120
+ **Decision contract**:
121
+ - Default mode: warning-only (exits 0). Writes guidance to stderr.
122
+ - Strict mode (`FLOW_AGENTS_GOAL_FIT_STRICT=true`): blocking (exits 2) when the active workflow artifact has state, Definition Of Done, Goal Fit, or sidecar issues that classify as blocking.
123
+
124
+ **Degradation when host lacks trigger**: If the host has no stop hook, stop-goal-fit cannot fire. The agent may complete without the check. Log the gap as `stop: no native equivalent — stop-goal-fit policy unavailable`.
125
+
126
+ ### 2.4 Config Protection
127
+
128
+ **Intent**: Block the agent from weakening linter and formatter configuration files. Steers the agent to fix source code instead.
129
+
130
+ **Canonical script**: `scripts/hooks/config-protection.js`
131
+
132
+ **Canonical trigger event**: `preToolUse` (on write/edit tool calls)
133
+
134
+ **Inputs consumed**:
135
+ - `tool_input.path` or `tool_input.file_path` — the target file path
136
+ - `SA_HOOK_INPUT_TRUNCATED` env var — whether input was truncated (truncated payloads are blocked unconditionally)
137
+ - Protected file set: `.eslintrc*`, `eslint.config.*`, `.prettierrc*`, `prettier.config.*`, `biome.json`, `biome.jsonc`, `.ruff.toml`, `ruff.toml`, `.shellcheckrc`, `.stylelintrc*`, `.markdownlint*`
138
+
139
+ **Decision contract**: Blocking (exits 2) when the target file basename is in the protected set. Writes a descriptive message to stderr directing the agent to fix source instead. Exits 0 (allow) otherwise.
140
+
141
+ **Degradation when host lacks trigger**: If the host has no `preToolUse`-equivalent blocking hook, config protection cannot veto tool calls. The agent may modify linter configs without interception. Log the gap as `preToolUse: no native blocking equivalent — config-protection policy unavailable`.
142
+
143
+ ---
144
+
145
+ ## 3. Hook Profiles
146
+
147
+ The `run-hook.js` runner supports a three-level profile system, controlled by the `SA_HOOK_PROFILE` environment variable.
148
+
149
+ | Profile | Behavior |
150
+ | --- | --- |
151
+ | `minimal` | Hooks in the `minimal` allowlist run. All others are skipped. |
152
+ | `standard` | Default. Hooks targeting `standard` or `strict` profiles run. Most hooks use this profile. |
153
+ | `strict` | All hooks run. Stricter behavior (e.g., `SA_QUALITY_GATE_STRICT=true`) may be implied. |
154
+
155
+ Individual hooks may be disabled by listing their IDs in `SA_DISABLED_HOOKS` (comma-separated). Adapters should expose these controls through their configuration surface.
156
+
157
+ ---
158
+
159
+ ## 4. Conformance Levels
160
+
161
+ Conformance levels define what a host adapter implements. These levels are intended to be referenced by the runtime matrix in product and marketing materials.
162
+
163
+ ### L0: Telemetry-Only
164
+
165
+ The adapter wires the canonical telemetry script (`scripts/telemetry/telemetry.sh` via a language-specific wrapper) to at least one lifecycle event. No policy hooks are wired.
166
+
167
+ **Required**: At minimum, `agentSpawn` telemetry fires on session start.
168
+
169
+ **Permitted gaps**: All policy classes (workflow steering, quality gate, stop-goal-fit, config protection) are absent.
170
+
171
+ **Use case**: Framework adapters and runtimes where the telemetry signal is valuable but blocking or injecting context is not feasible.
172
+
173
+ ### L1: Steering
174
+
175
+ The adapter implements L0 plus workflow steering context injection and, where the host has a stop hook, stop-goal-fit in warning mode.
176
+
177
+ **Required**:
178
+ - L0 telemetry.
179
+ - Workflow steering fires on `userPromptSubmit` (or the closest equivalent; document which event is used and any fidelity loss).
180
+ - Stop-goal-fit fires on `stop` in warning-only mode (exits 0 always).
181
+
182
+ **Permitted gaps**: Quality gate and config protection are absent. Stop-goal-fit runs in warning mode only.
183
+
184
+ **Use case**: Harness adapters where the runtime supports prompt-submit and stop hooks, but tool-level blocking is not available or desired.
185
+
186
+ ### L2: Enforcing Gates
187
+
188
+ The adapter implements L1 plus all blocking policy classes.
189
+
190
+ **Required**:
191
+ - L1 steering and stop telemetry.
192
+ - Config protection fires on `preToolUse` and can block (exit 2 translates to a deny response).
193
+ - Quality gate fires on `postToolUse`.
194
+ - Stop-goal-fit fires on `stop` with `FLOW_AGENTS_GOAL_FIT_STRICT` configurable (default may be warning mode; strict mode must be possible to enable).
195
+
196
+ **Permitted gaps**: None. All four policy classes are wired. Any missing host trigger must be documented as a named gap in the adapter's conformance declaration.
197
+
198
+ **Use case**: Claude Code (current reference implementation), Codex (current reference implementation). The target conformance level for new harness adapters.
199
+
200
+ ---
201
+
202
+ ## 5. Mapping Tables
203
+
204
+ The following tables show the canonical Flow Agents events and their corresponding host-native event surfaces. "No native equivalent" entries are honest gaps, not future work unless noted.
205
+
206
+ ### 5.1 Canonical Event to Host Surface
207
+
208
+ | Canonical Event | Claude Code | Codex | Kiro | opencode | pi |
209
+ | --- | --- | --- | --- | --- | --- |
210
+ | `agentSpawn` | `SessionStart` | `SessionStart` | `SessionStart` | `session.created` | `session_start` |
211
+ | `userPromptSubmit` | `UserPromptSubmit` | `UserPromptSubmit` | `UserPromptSubmit` | No native equivalent | `input` (closest; fires on user input, not confirmed submission) |
212
+ | `preToolUse` | `PreToolUse` | `PreToolUse` | `PreToolUse` | `tool.execute.before` | `tool_call` (blockable) |
213
+ | `permissionRequest` | `PermissionRequest` | `PermissionRequest` | No native equivalent | No native equivalent | No native equivalent |
214
+ | `postToolUse` | `PostToolUse`, `PostToolUseFailure` | `PostToolUse` | `PostToolUse` | `tool.execute.after` | `tool_result` |
215
+ | `stop` | `Stop`, `SessionEnd` | `Stop` | `Stop` | `session.idle` (closest; not a true stop event) | No native equivalent |
216
+ | `subagentStart` | `SubagentStart` | No native equivalent | No native equivalent | No native equivalent | No native equivalent |
217
+ | `subagentStop` | `SubagentStop` | No native equivalent | No native equivalent | No native equivalent | No native equivalent |
218
+
219
+ ### 5.2 Canonical Policy to Host Blocking Capability
220
+
221
+ | Policy Class | Claude Code | Codex | Kiro | opencode | pi |
222
+ | --- | --- | --- | --- | --- | --- |
223
+ | Workflow steering (inject) | `UserPromptSubmit` additionalContext | `UserPromptSubmit` additionalContext | `UserPromptSubmit` context | No prompt-submit hook — gap (see section 2.1) | `input` (reduced fidelity) |
224
+ | Quality gate (warn) | `PostToolUse` additionalContext | `PostToolUse` additionalContext | `PostToolUse` | `tool.execute.after` | `tool_result` |
225
+ | Stop-goal-fit (warn/block) | `Stop` — can block | `Stop` — can block | `Stop` — can block | `session.idle` — no block capability | No stop hook — gap |
226
+ | Config protection (block) | `PreToolUse` deny | `PreToolUse` deny | `PreToolUse` deny | `tool.execute.before` deny | `tool_call` block |
227
+
228
+ ### 5.3 Framework Adapter Mapping
229
+
230
+ | Canonical Event | AWS Strands | VoltAgent | LangGraph | OpenAI Agents SDK |
231
+ | --- | --- | --- | --- | --- |
232
+ | `agentSpawn` | `BeforeInvocationEvent` | `onStart` | `on_chain_start` | Lifecycle: `on_agent_start` |
233
+ | `userPromptSubmit` | No native equivalent — inject via system prompt at invocation | No native equivalent | `on_chain_start` (partial) | No native equivalent |
234
+ | `preToolUse` | `BeforeToolCallEvent` (cancellable) | `onToolStart` | `on_tool_start` | No native blocking equivalent |
235
+ | `postToolUse` | `AfterToolCallEvent` | `onToolEnd` | `on_tool_end` | No native equivalent |
236
+ | `stop` | `AfterInvocationEvent` | `onEnd` | `on_chain_end` | `on_agent_end` |
237
+ | Context injection | `MessageAddedEvent` (Strands: message context) | No native equivalent | Callback / middleware | Guardrails (input/output) |
238
+
239
+ **Note on AWS Strands**: The `BeforeToolCallEvent` is cancellable, which maps directly to the blocking policy contract. The `MessageAddedEvent` may be used to approximate workflow steering context injection. A Strands spike is being scaffolded under `integrations/strands/`.
240
+
241
+ ---
242
+
243
+ ## 6. Distribution Models
244
+
245
+ ### 6.1 Harness Adapters (File Install via Bundles)
246
+
247
+ Harness adapters are file sets that the `npm run build:bundles` command generates and the host-specific install script places on disk. The bundle builder (`src/tools/build-universal-bundles.ts`) generates the host config files from the canonical manifest and policy scripts.
248
+
249
+ **What an adapter must implement**:
250
+
251
+ 1. **Event wiring config** — A host-specific configuration file (e.g., `.claude/settings.json` hooks object, `.codex/hooks.json`) that maps host event names to shell commands that invoke the canonical hook adapter wrapper.
252
+ 2. **Adapter wrapper** — A host-specific JS (or equivalent) wrapper (e.g., `claude-hook-adapter.js`, `codex-hook-adapter.js`) that:
253
+ - Reads stdin JSON from the host.
254
+ - Invokes `run-hook.js` with the canonical script path and profile.
255
+ - Translates the exit code and output to the host-native hook response format.
256
+ - Fails open on errors (never blocks work due to hook runtime failure).
257
+ 3. **Telemetry wrapper** — A host-specific wrapper (e.g., `claude-telemetry-hook.js`, `codex-telemetry-hook.js`) that:
258
+ - Maps host event names to canonical telemetry event names.
259
+ - Invokes `scripts/telemetry/telemetry.sh` with the canonical event name and agent name.
260
+ - Emits a valid host-native hook response (telemetry is always non-blocking).
261
+ 4. **Install script** — A shell script that places the generated files at the host-expected paths and applies any path token substitution (e.g., `__KIRO_PACKAGE_ROOT__`).
262
+
263
+ **Generated hook command pattern** (from `src/tools/build-universal-bundles.ts` lines 198–242):
264
+
265
+ ```
266
+ # Telemetry (fires first, always non-blocking):
267
+ bash -lc 'root="${<HOST_ROOT_VAR>:-$(pwd)}"; node "$root/scripts/hooks/<host>-telemetry-hook.js" <EventName> dev'
268
+
269
+ # Policy (fires second, may block on preToolUse):
270
+ bash -lc 'root="${<HOST_ROOT_VAR>:-$(pwd)}"; node "$root/scripts/hooks/<host>-hook-adapter.js" <EventName> <hook-id> <script.js> default'
271
+ ```
272
+
273
+ **Timeout defaults**: Telemetry hooks default to 10 seconds. Policy hooks default to 30 seconds. Override via `FLOW_AGENTS_<RUNTIME>_HOOK_TIMEOUT_MS` and `FLOW_AGENTS_<RUNTIME>_TELEMETRY_TIMEOUT_MS`.
274
+
275
+ **Environment variable set by adapters**:
276
+
277
+ | Variable | Value | Purpose |
278
+ | --- | --- | --- |
279
+ | `FLOW_AGENTS_HOOK_RUNTIME` | `claude-code`, `codex`, etc. | Identifies the runtime to downstream hooks |
280
+ | `FLOW_AGENTS_TELEMETRY_RUNTIME` | `claude-code`, `codex`, etc. | Identifies the runtime to telemetry |
281
+ | `SA_HOOK_INPUT_TRUNCATED` | `0` or `1` | Whether the stdin payload was truncated at `MAX_STDIN` (1 MiB) |
282
+ | `SA_HOOK_INPUT_MAX_BYTES` | integer string | The truncation threshold in bytes |
283
+
284
+ ### 6.2 Framework Adapters (Language-Native Package)
285
+
286
+ Framework adapters are in-process packages (npm, PyPI, Maven, etc.) that register Flow Agents hooks with the framework's lifecycle system using the framework's native registration API.
287
+
288
+ **What an adapter must implement**:
289
+
290
+ 1. **Lifecycle registration** — Register callbacks with the framework at the hook points that correspond to canonical events (see mapping table, section 5.3).
291
+ 2. **Canonical script invocation** — When a registered callback fires, invoke the corresponding canonical hook script (`scripts/hooks/<policy>.js`) via a subprocess or by calling the exported `run()` function directly (preferred for performance — see `run-hook.js` for the `module.exports.run` fast path).
292
+ 3. **Blocking translation** — For blocking-capable hooks (e.g., `BeforeToolCallEvent` in Strands, `tool_call` in pi), translate exit code 2 from the canonical script into the framework-native cancellation or deny signal.
293
+ 4. **Fail-open error handling** — If the canonical script exits with a non-0, non-2 code, or fails to execute, treat the result as allow (pass through). Never allow hook runtime errors to block agent work.
294
+ 5. **Telemetry dispatch** — For each canonical event, invoke `scripts/telemetry/telemetry.sh <canonical-event-name> <agent-name>` with the JSON payload on stdin. Telemetry must not block the framework callback chain.
295
+ 6. **Profile and disable controls** — Honor `SA_HOOK_PROFILE` (minimal/standard/strict) and `SA_DISABLED_HOOKS` either by passing them as environment variables to the subprocess or by implementing the same logic from `scripts/hooks/lib/hook-flags.js` natively.
296
+
297
+ **Minimum viable framework adapter** (pseudocode):
298
+
299
+ ```
300
+ on_before_tool_call(event):
301
+ result = invoke_canonical("config-protection.js", event.as_json())
302
+ if result.exit_code == 2:
303
+ event.cancel(reason=result.stderr)
304
+ dispatch_telemetry("preToolUse", event.as_json()) # non-blocking
305
+
306
+ on_after_tool_call(event):
307
+ invoke_canonical("quality-gate.js", event.as_json()) # non-blocking
308
+ dispatch_telemetry("postToolUse", event.as_json())
309
+
310
+ on_agent_start(event):
311
+ dispatch_telemetry("agentSpawn", event.as_json())
312
+
313
+ on_agent_end(event):
314
+ invoke_canonical("stop-goal-fit.js", event.as_json()) # blocking if STRICT=true
315
+ dispatch_telemetry("stop", event.as_json())
316
+ ```
317
+
318
+ ---
319
+
320
+ ## 7. Conformance Declaration
321
+
322
+ Each adapter must include a conformance declaration in its adapter documentation or metadata. The declaration must name:
323
+
324
+ 1. **Conformance level**: L0, L1, or L2.
325
+ 2. **Host runtime**: The target host name and version range.
326
+ 3. **Canonical event coverage**: Which canonical events are wired and which are gaps, using the naming from section 1.
327
+ 4. **Policy coverage**: Which policy classes are wired, at which trigger points, and with what blocking capability.
328
+ 5. **Named gaps**: For every "no native equivalent" mapping, state the gap explicitly, describe the degradation behavior, and note whether any lower-fidelity approximation is used.
329
+
330
+ **Example** (opencode adapter, L1):
331
+
332
+ ```
333
+ conformance_level: L1
334
+ host: opencode
335
+ event_coverage:
336
+ agentSpawn: session.created (full fidelity)
337
+ userPromptSubmit: no native equivalent — workflow steering unavailable at turn boundary
338
+ preToolUse: tool.execute.before (full fidelity, blocking available)
339
+ postToolUse: tool.execute.after (full fidelity)
340
+ stop: session.idle (reduced fidelity — not a true stop; fires on idle, not completion)
341
+ permissionRequest: no native equivalent
342
+ subagentStart: no native equivalent
343
+ subagentStop: no native equivalent
344
+ policy_coverage:
345
+ workflow_steering: partial — injected at session.created only, not at each turn
346
+ quality_gate: wired at tool.execute.after
347
+ stop_goal_fit: degraded — session.idle does not reliably fire at completion
348
+ config_protection: wired at tool.execute.before (blocking)
349
+ ```
350
+
351
+ ---
352
+
353
+ ## Related Documents
354
+
355
+ - [Developer Architecture](../developer-architecture.html) — coordination map and product boundaries.
356
+ - [Operating Layers](../operating-layers.html) — layer model and placement rules.
357
+ - [Eval Strategy](../workflow-eval-strategy.html) — how hook influence behavior is validated.
358
+ - [Developer Hook Setup](../developer-hook-setup.html) — runtime boundary for repo-local git hooks vs. runtime hooks.
359
+ - `scripts/hooks/` — canonical policy implementations.
360
+ - `src/tools/build-universal-bundles.ts` — bundle generation logic including hook command patterns.
361
+ - `evals/fixtures/hook-influence/cases.json` — behavioral cases that define expected agent responses to hook guidance.
362
+
363
+ ---
364
+
365
+ ## 8. Engine Contract (contract_version "1.0")
366
+
367
+ This section is the versioned public contract for the Flow Agents policy engine. Third-party adapters bind to this contract. Breaking changes will increment the major version and be announced via CHANGELOG.
368
+
369
+ ### 8.1 Invocation forms
370
+
371
+ **Form 1 — Subprocess CLI** (the standard form, used by all current adapters):
372
+
373
+ ```
374
+ echo '<JSON payload>' | node scripts/hooks/run-hook.js <hookId> <scriptRelativePath> [profilesCsv]
375
+ ```
376
+
377
+ - `hookId`: an identifier string for the hook (e.g., `config-protection`). Used for profile/disable checks.
378
+ - `scriptRelativePath`: path relative to `scripts/hooks/` (e.g., `config-protection.js`).
379
+ - `profilesCsv`: comma-separated profile names that must include the current `SA_HOOK_PROFILE` value (default `standard`). Hooks not in the allowed profiles are skipped (fail-open).
380
+ - Payload is read from stdin. Max 1 MiB (`SA_HOOK_INPUT_MAX_BYTES`). If truncated, `SA_HOOK_INPUT_TRUNCATED=1` is set.
381
+
382
+ **Form 2 — Native import** (for TypeScript/Node.js adapters, preferred for performance):
383
+
384
+ ```javascript
385
+ const { run } = require('./scripts/hooks/config-protection.js');
386
+ const output = run(rawJsonString, { truncated: false, maxStdin: 1024 * 1024 });
387
+ // output: string (pass-through) | { exitCode, stderr?, stdout? } (structured)
388
+ ```
389
+
390
+ All four policy scripts export `module.exports = { run }`.
391
+
392
+ **Version query** (additive, backward-compatible):
393
+
394
+ ```
395
+ node scripts/hooks/run-hook.js --contract-version
396
+ # → {"contract_version":"1.0","runner":"run-hook.js"}
397
+ ```
398
+
399
+ ### 8.2 Payload schema per canonical event
400
+
401
+ All payloads are a single JSON object on stdin. Required and optional fields:
402
+
403
+ | Canonical event | Required fields | Optional fields |
404
+ |----------------|-----------------|-----------------|
405
+ | `preToolUse` | `hook_event_name` | `tool_name`, `tool_input.path`, `tool_input.file_path`, `cwd` |
406
+ | `postToolUse` | `hook_event_name` | `tool_name`, `tool_input.path`, `tool_input.file_path`, `tool_response`, `cwd` |
407
+ | `userPromptSubmit` | `hook_event_name` | `tool_input` (for subagent calls), `cwd` |
408
+ | `stop` | `hook_event_name` | `cwd`, `stop_reason` |
409
+
410
+ `hook_event_name` is the **host-native** event name (e.g., `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`) or may be omitted — policy scripts read the canonical fields (`tool_input.path`, `cwd`) directly and do not require the event name field for their decisions.
411
+
412
+ ### 8.3 Decision schema (stdout / exit code)
413
+
414
+ | Exit code | Semantics | Stdout |
415
+ |-----------|-----------|--------|
416
+ | `0` | **Allow** — policy has no objection | Echo of raw input JSON (or input + appended guidance for steering) |
417
+ | `2` | **Block** — policy vetoes the action | Empty or irrelevant (adapters use stderr message for the block reason) |
418
+ | other | **Error** — hook runtime failure | Treat as allow (fail-open). Never block agent work on hook errors. |
419
+
420
+ For steering/quality hooks that return guidance, the output format is:
421
+
422
+ ```
423
+ <original input JSON>\n\n---\n<guidance text>\n---
424
+ ```
425
+
426
+ For structured `run()` responses (native import form), the return value is:
427
+ - A string: treated as the full stdout replacement.
428
+ - `{ exitCode, stderr?, stdout? }`: `exitCode` drives allow/block; `stderr` is written to stderr; `stdout` overrides stdout (if absent, raw input is echoed on allow).
429
+
430
+ ### 8.4 Fail-open vs. fail-closed rules per policy class
431
+
432
+ | Policy class | Default mode | Fail-open on error? | Blocking capable? |
433
+ |-------------|-------------|--------------------|--------------------|
434
+ | config-protection | Fail-closed (exit 2 on protected file) | Yes — hook runtime errors exit 0 | Yes (preToolUse) |
435
+ | quality-gate | Fail-open (exit 0 always) | Yes | No |
436
+ | stop-goal-fit | Fail-open by default; fail-closed with `FLOW_AGENTS_GOAL_FIT_STRICT=true` | Yes — hook runtime errors exit 0 | Yes (stop, strict mode only) |
437
+ | workflow-steering | Fail-open (exit 0 always) | Yes | No |
438
+
439
+ **Telemetry**: Always fail-open. Hook runtime errors in telemetry scripts must never block agent work.
440
+
441
+ **Truncated payloads**: config-protection exits 2 (block) when `SA_HOOK_INPUT_TRUNCATED=1`, because it cannot safely evaluate an incomplete payload. All other policies fail-open on truncated input.
442
+
443
+ ### 8.5 Environment variables consumed by the engine
444
+
445
+ | Variable | Values | Consumed by |
446
+ |----------|--------|-------------|
447
+ | `SA_HOOK_PROFILE` | `minimal` \| `standard` (default) \| `strict` | `run-hook.js` |
448
+ | `SA_DISABLED_HOOKS` | Comma-separated hook IDs | `run-hook.js` |
449
+ | `SA_HOOK_INPUT_TRUNCATED` | `0` or `1` | `config-protection.js` |
450
+ | `SA_HOOK_INPUT_MAX_BYTES` | Integer string | `config-protection.js` |
451
+ | `SA_QUALITY_GATE_FIX` | `true` / `false` | `quality-gate.js` |
452
+ | `SA_QUALITY_GATE_STRICT` | `true` / `false` | `quality-gate.js` |
453
+ | `FLOW_AGENTS_GOAL_FIT_STRICT` | `true` / `false` | `stop-goal-fit.js` |
454
+ | `FLOW_AGENTS_REQUIRE_SIDECARS` | `true` / `false` | `stop-goal-fit.js` |
455
+ | `FLOW_AGENTS_REQUIRE_CRITIQUE` | `true` / `false` | `stop-goal-fit.js` |
456
+ | `FLOW_AGENTS_HOOK_RUNTIME` | `claude-code`, `codex`, etc. | Hook adapters (forwarded to scripts) |
457
+
458
+ ### 8.6 Self-certification via the conformance kit
459
+
460
+ The `packaging/conformance/` directory contains golden fixtures and a test runner. To self-certify:
461
+
462
+ ```bash
463
+ # Verify the canonical engine reaches L2 (required to pass):
464
+ node packaging/conformance/run-conformance.js --self
465
+
466
+ # Test a third-party adapter at L1:
467
+ node packaging/conformance/run-conformance.js \
468
+ --adapter-cmd "node /path/to/your-adapter.js" \
469
+ --level L1
470
+ ```
471
+
472
+ See `packaging/conformance/README.md` for the full fixture inventory and declaration format.