@open-agent-toolkit/cli 0.2.50 → 0.2.52

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 (263) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/bootstrap.md +6 -2
  3. package/assets/docs/cli-utilities/tool-packs.md +87 -33
  4. package/assets/docs/provider-sync/commands.md +30 -2
  5. package/assets/docs/provider-sync/config.md +19 -0
  6. package/assets/docs/provider-sync/manifest-and-drift.md +37 -0
  7. package/assets/docs/provider-sync/providers.md +69 -2
  8. package/assets/docs/provider-sync/scope-and-surface.md +44 -0
  9. package/assets/docs/reference/cli-reference.md +21 -5
  10. package/assets/docs/reference/file-locations.md +8 -2
  11. package/assets/docs/reference/oat-directory-structure.md +25 -9
  12. package/assets/docs/workflows/projects/artifacts.md +9 -4
  13. package/assets/docs/workflows/projects/evidence-layers.md +110 -1
  14. package/assets/docs/workflows/projects/implementation-execution.md +13 -0
  15. package/assets/docs/workflows/projects/lifecycle.md +17 -5
  16. package/assets/docs/workflows/projects/orchestration-model.md +18 -0
  17. package/assets/docs/workflows/projects/picking-up-projects.md +26 -9
  18. package/assets/docs/workflows/projects/pr-flow.md +5 -3
  19. package/assets/docs/workflows/projects/reviewing-oat-prs.md +12 -8
  20. package/assets/public-package-versions.json +4 -4
  21. package/assets/skills/oat-dispatch-subagents/SKILL.md +57 -1
  22. package/assets/skills/oat-dispatch-subagents/references/record-schema.md +77 -1
  23. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +3 -3
  24. package/assets/skills/oat-project-complete/SKILL.md +193 -26
  25. package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +306 -0
  26. package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +125 -0
  27. package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +106 -0
  28. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +110 -0
  29. package/assets/skills/oat-project-complete/scripts/resolve-synced-archive-entry.mjs +164 -0
  30. package/assets/skills/oat-project-dispatch-subagents/SKILL.md +24 -1
  31. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +3 -3
  32. package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +11 -0
  33. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +3 -3
  34. package/assets/skills/oat-project-plan-writing/SKILL.md +12 -1
  35. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +3 -3
  36. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +3 -3
  37. package/assets/skills/oat-project-review-provide/SKILL.md +12 -1
  38. package/assets/skills/oat-project-review-provide-remote/SKILL.md +12 -1
  39. package/dist/agents/canonical/index.d.ts +2 -0
  40. package/dist/agents/canonical/index.d.ts.map +1 -1
  41. package/dist/agents/canonical/index.js +1 -0
  42. package/dist/agents/canonical/resolve.d.ts +45 -0
  43. package/dist/agents/canonical/resolve.d.ts.map +1 -0
  44. package/dist/agents/canonical/resolve.js +175 -0
  45. package/dist/commands/decision/agents-guidance.d.ts +2 -2
  46. package/dist/commands/decision/agents-guidance.d.ts.map +1 -1
  47. package/dist/commands/decision/agents-guidance.js +1 -4
  48. package/dist/commands/decision/index.d.ts.map +1 -1
  49. package/dist/commands/decision/index.js +25 -6
  50. package/dist/commands/docs/init/index.d.ts +7 -1
  51. package/dist/commands/docs/init/index.d.ts.map +1 -1
  52. package/dist/commands/docs/init/index.js +53 -20
  53. package/dist/commands/doctor/index.d.ts +8 -2
  54. package/dist/commands/doctor/index.d.ts.map +1 -1
  55. package/dist/commands/doctor/index.js +157 -46
  56. package/dist/commands/init/index.d.ts +8 -3
  57. package/dist/commands/init/index.d.ts.map +1 -1
  58. package/dist/commands/init/index.js +64 -26
  59. package/dist/commands/init/tools/index.d.ts +12 -12
  60. package/dist/commands/init/tools/index.d.ts.map +1 -1
  61. package/dist/commands/init/tools/index.js +430 -99
  62. package/dist/commands/init/tools/install-state.d.ts +2 -0
  63. package/dist/commands/init/tools/install-state.d.ts.map +1 -1
  64. package/dist/commands/init/tools/install-state.js +11 -0
  65. package/dist/commands/init/tools/project-guidance.d.ts +49 -0
  66. package/dist/commands/init/tools/project-guidance.d.ts.map +1 -0
  67. package/dist/commands/init/tools/project-guidance.js +144 -0
  68. package/dist/commands/init/tools/workflows/index.d.ts +7 -0
  69. package/dist/commands/init/tools/workflows/index.d.ts.map +1 -1
  70. package/dist/commands/init/tools/workflows/index.js +160 -7
  71. package/dist/commands/pjm/index.d.ts.map +1 -1
  72. package/dist/commands/pjm/index.js +28 -3
  73. package/dist/commands/pjm/init.d.ts +5 -0
  74. package/dist/commands/pjm/init.d.ts.map +1 -1
  75. package/dist/commands/pjm/init.js +19 -3
  76. package/dist/commands/project/archive/archive-utils.d.ts +8 -1
  77. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  78. package/dist/commands/project/archive/archive-utils.js +147 -50
  79. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  80. package/dist/commands/project/archive/push-runner.js +15 -2
  81. package/dist/commands/project/dispatch/index.d.ts +10 -0
  82. package/dist/commands/project/dispatch/index.d.ts.map +1 -0
  83. package/dist/commands/project/dispatch/index.js +137 -0
  84. package/dist/commands/project/dispatch/record.d.ts +91 -0
  85. package/dist/commands/project/dispatch/record.d.ts.map +1 -0
  86. package/dist/commands/project/dispatch/record.js +402 -0
  87. package/dist/commands/project/index.d.ts.map +1 -1
  88. package/dist/commands/project/index.js +2 -0
  89. package/dist/commands/project/links/compute.d.ts +1 -0
  90. package/dist/commands/project/links/compute.d.ts.map +1 -1
  91. package/dist/commands/project/links/compute.js +5 -4
  92. package/dist/commands/project/links/index.d.ts +9 -0
  93. package/dist/commands/project/links/index.d.ts.map +1 -1
  94. package/dist/commands/project/links/index.js +71 -2
  95. package/dist/commands/project/list.d.ts +10 -2
  96. package/dist/commands/project/list.d.ts.map +1 -1
  97. package/dist/commands/project/list.js +205 -29
  98. package/dist/commands/project/open/index.d.ts +2 -0
  99. package/dist/commands/project/open/index.d.ts.map +1 -1
  100. package/dist/commands/project/open/index.js +16 -1
  101. package/dist/commands/project/prune/index.d.ts +10 -4
  102. package/dist/commands/project/prune/index.d.ts.map +1 -1
  103. package/dist/commands/project/prune/index.js +85 -15
  104. package/dist/commands/project/pull/index.d.ts +4 -0
  105. package/dist/commands/project/pull/index.d.ts.map +1 -1
  106. package/dist/commands/project/pull/index.js +82 -3
  107. package/dist/commands/project/sync/git.d.ts +9 -0
  108. package/dist/commands/project/sync/git.d.ts.map +1 -1
  109. package/dist/commands/project/sync/git.js +18 -0
  110. package/dist/commands/project/sync/ref-sync.d.ts +16 -1
  111. package/dist/commands/project/sync/ref-sync.d.ts.map +1 -1
  112. package/dist/commands/project/sync/ref-sync.js +288 -17
  113. package/dist/commands/project/sync/resolve-target.d.ts +10 -0
  114. package/dist/commands/project/sync/resolve-target.d.ts.map +1 -1
  115. package/dist/commands/project/sync/resolve-target.js +56 -1
  116. package/dist/commands/providers/inspect/inspect.d.ts +8 -1
  117. package/dist/commands/providers/inspect/inspect.d.ts.map +1 -1
  118. package/dist/commands/providers/inspect/inspect.js +146 -26
  119. package/dist/commands/providers/list/list.d.ts +8 -1
  120. package/dist/commands/providers/list/list.d.ts.map +1 -1
  121. package/dist/commands/providers/list/list.js +139 -23
  122. package/dist/commands/providers/providers.types.d.ts +25 -1
  123. package/dist/commands/providers/providers.types.d.ts.map +1 -1
  124. package/dist/commands/providers/set/index.d.ts +9 -1
  125. package/dist/commands/providers/set/index.d.ts.map +1 -1
  126. package/dist/commands/providers/set/index.js +43 -33
  127. package/dist/commands/remove/skill/remove-skill.d.ts.map +1 -1
  128. package/dist/commands/remove/skill/remove-skill.js +11 -0
  129. package/dist/commands/shared/agents-md.d.ts +42 -14
  130. package/dist/commands/shared/agents-md.d.ts.map +1 -1
  131. package/dist/commands/shared/agents-md.js +364 -70
  132. package/dist/commands/shared/project-scope.d.ts +2 -0
  133. package/dist/commands/shared/project-scope.d.ts.map +1 -1
  134. package/dist/commands/shared/project-scope.js +5 -0
  135. package/dist/commands/state/generate.d.ts +2 -0
  136. package/dist/commands/state/generate.d.ts.map +1 -1
  137. package/dist/commands/state/generate.js +93 -28
  138. package/dist/commands/status/index.d.ts +8 -3
  139. package/dist/commands/status/index.d.ts.map +1 -1
  140. package/dist/commands/status/index.js +147 -32
  141. package/dist/commands/sync/apply.d.ts.map +1 -1
  142. package/dist/commands/sync/apply.js +220 -29
  143. package/dist/commands/sync/dry-run.d.ts.map +1 -1
  144. package/dist/commands/sync/dry-run.js +30 -6
  145. package/dist/commands/sync/index.d.ts +17 -2
  146. package/dist/commands/sync/index.d.ts.map +1 -1
  147. package/dist/commands/sync/index.js +106 -41
  148. package/dist/commands/sync/sync.types.d.ts +39 -2
  149. package/dist/commands/sync/sync.types.d.ts.map +1 -1
  150. package/dist/commands/sync/sync.utils.d.ts +5 -1
  151. package/dist/commands/sync/sync.utils.d.ts.map +1 -1
  152. package/dist/commands/sync/sync.utils.js +73 -0
  153. package/dist/commands/tools/info/info-tool.d.ts +2 -0
  154. package/dist/commands/tools/info/info-tool.d.ts.map +1 -1
  155. package/dist/commands/tools/info/info-tool.js +34 -12
  156. package/dist/commands/tools/install/index.d.ts.map +1 -1
  157. package/dist/commands/tools/install/index.js +19 -3
  158. package/dist/commands/tools/list/list-tools.d.ts +2 -0
  159. package/dist/commands/tools/list/list-tools.d.ts.map +1 -1
  160. package/dist/commands/tools/list/list-tools.js +36 -7
  161. package/dist/commands/tools/remove/index.d.ts.map +1 -1
  162. package/dist/commands/tools/remove/index.js +150 -21
  163. package/dist/commands/tools/remove/remove-tools.d.ts +20 -0
  164. package/dist/commands/tools/remove/remove-tools.d.ts.map +1 -1
  165. package/dist/commands/tools/remove/remove-tools.js +122 -1
  166. package/dist/commands/tools/shared/format-pack-inventory.d.ts +19 -0
  167. package/dist/commands/tools/shared/format-pack-inventory.d.ts.map +1 -1
  168. package/dist/commands/tools/shared/format-pack-inventory.js +109 -0
  169. package/dist/commands/tools/shared/pack-evidence.d.ts +66 -0
  170. package/dist/commands/tools/shared/pack-evidence.d.ts.map +1 -0
  171. package/dist/commands/tools/shared/pack-evidence.js +132 -0
  172. package/dist/commands/tools/shared/pack-inventory.d.ts +1 -0
  173. package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
  174. package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts +44 -0
  175. package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts.map +1 -0
  176. package/dist/commands/tools/shared/pack-lifecycle-outcome.js +91 -0
  177. package/dist/commands/tools/update/index.d.ts.map +1 -1
  178. package/dist/commands/tools/update/index.js +74 -4
  179. package/dist/commands/tools/update/update-tools.d.ts +5 -0
  180. package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
  181. package/dist/commands/tools/update/update-tools.js +64 -2
  182. package/dist/config/oat-config.d.ts +5 -0
  183. package/dist/config/oat-config.d.ts.map +1 -1
  184. package/dist/config/oat-config.js +17 -5
  185. package/dist/config/user-sync-config.d.ts +1 -0
  186. package/dist/config/user-sync-config.d.ts.map +1 -1
  187. package/dist/config/user-sync-config.js +8 -0
  188. package/dist/drift/detector.d.ts +2 -2
  189. package/dist/drift/detector.d.ts.map +1 -1
  190. package/dist/drift/detector.js +18 -0
  191. package/dist/drift/strays.d.ts.map +1 -1
  192. package/dist/drift/strays.js +5 -0
  193. package/dist/engine/collection-sync.d.ts +14 -0
  194. package/dist/engine/collection-sync.d.ts.map +1 -0
  195. package/dist/engine/collection-sync.js +282 -0
  196. package/dist/engine/compute-plan.d.ts +8 -1
  197. package/dist/engine/compute-plan.d.ts.map +1 -1
  198. package/dist/engine/compute-plan.js +303 -12
  199. package/dist/engine/engine.types.d.ts +61 -0
  200. package/dist/engine/engine.types.d.ts.map +1 -1
  201. package/dist/engine/execute-plan.d.ts +22 -2
  202. package/dist/engine/execute-plan.d.ts.map +1 -1
  203. package/dist/engine/execute-plan.js +459 -27
  204. package/dist/engine/provider-path-safety.d.ts +1 -0
  205. package/dist/engine/provider-path-safety.d.ts.map +1 -1
  206. package/dist/engine/provider-path-safety.js +15 -0
  207. package/dist/engine/scanner.d.ts +2 -2
  208. package/dist/engine/scanner.d.ts.map +1 -1
  209. package/dist/engine/scanner.js +21 -4
  210. package/dist/fs/io.d.ts +67 -0
  211. package/dist/fs/io.d.ts.map +1 -1
  212. package/dist/fs/io.js +536 -3
  213. package/dist/manifest/index.d.ts +2 -2
  214. package/dist/manifest/index.d.ts.map +1 -1
  215. package/dist/manifest/index.js +1 -1
  216. package/dist/manifest/manager.d.ts +2 -2
  217. package/dist/manifest/manager.d.ts.map +1 -1
  218. package/dist/manifest/manager.js +20 -3
  219. package/dist/manifest/manifest.types.d.ts +704 -13
  220. package/dist/manifest/manifest.types.d.ts.map +1 -1
  221. package/dist/manifest/manifest.types.js +131 -15
  222. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  223. package/dist/providers/codex/codec/sync-extension.js +41 -10
  224. package/dist/providers/cursor/codec/sync-extension.d.ts.map +1 -1
  225. package/dist/providers/cursor/codec/sync-extension.js +41 -10
  226. package/dist/providers/identity/absolute-paths.d.ts +68 -0
  227. package/dist/providers/identity/absolute-paths.d.ts.map +1 -0
  228. package/dist/providers/identity/absolute-paths.js +144 -0
  229. package/dist/providers/identity/claude-runtime-observation.d.ts +105 -0
  230. package/dist/providers/identity/claude-runtime-observation.d.ts.map +1 -0
  231. package/dist/providers/identity/claude-runtime-observation.fixtures.d.ts +40 -0
  232. package/dist/providers/identity/claude-runtime-observation.fixtures.d.ts.map +1 -0
  233. package/dist/providers/identity/claude-runtime-observation.fixtures.js +146 -0
  234. package/dist/providers/identity/claude-runtime-observation.js +229 -0
  235. package/dist/providers/identity/codex-runtime-observation.d.ts +75 -0
  236. package/dist/providers/identity/codex-runtime-observation.d.ts.map +1 -0
  237. package/dist/providers/identity/codex-runtime-observation.fixtures.d.ts +37 -0
  238. package/dist/providers/identity/codex-runtime-observation.fixtures.d.ts.map +1 -0
  239. package/dist/providers/identity/codex-runtime-observation.fixtures.js +539 -0
  240. package/dist/providers/identity/codex-runtime-observation.js +255 -0
  241. package/dist/providers/identity/generic-dispatch-record.d.ts +313 -0
  242. package/dist/providers/identity/generic-dispatch-record.d.ts.map +1 -0
  243. package/dist/providers/identity/generic-dispatch-record.js +539 -0
  244. package/dist/providers/identity/oat-dispatch-record.d.ts +1964 -0
  245. package/dist/providers/identity/oat-dispatch-record.d.ts.map +1 -0
  246. package/dist/providers/identity/oat-dispatch-record.js +691 -0
  247. package/dist/providers/identity/runtime-observation.d.ts +100 -0
  248. package/dist/providers/identity/runtime-observation.d.ts.map +1 -0
  249. package/dist/providers/identity/runtime-observation.js +169 -0
  250. package/dist/providers/shared/index.d.ts +2 -0
  251. package/dist/providers/shared/index.d.ts.map +1 -1
  252. package/dist/providers/shared/index.js +1 -0
  253. package/dist/providers/shared/materialization-extension.d.ts +12 -0
  254. package/dist/providers/shared/materialization-extension.d.ts.map +1 -1
  255. package/dist/providers/shared/registry.d.ts +69 -0
  256. package/dist/providers/shared/registry.d.ts.map +1 -0
  257. package/dist/providers/shared/registry.js +252 -0
  258. package/dist/providers/shared/restart-adviser.d.ts +26 -0
  259. package/dist/providers/shared/restart-adviser.d.ts.map +1 -0
  260. package/dist/providers/shared/restart-adviser.js +96 -0
  261. package/dist/shared/types.d.ts +4 -0
  262. package/dist/shared/types.d.ts.map +1 -1
  263. package/package.json +2 -2
@@ -92,8 +92,10 @@ Config ownership note:
92
92
  - Synced checkout: `.oat/projects/synced/<project>/` (gitignored nested
93
93
  worktree)
94
94
  - Synced record: `.oat/projects/synced/<project>.json` (tracked on the parent
95
- branch)
96
- - Synced ref: `refs/oat/projects/<project>` on `origin`
95
+ branch while the project is active; removed after durable archived closeout)
96
+ - Active synced ref: `refs/oat/projects/<project>` on `origin`
97
+ - Completed synced ref: `refs/oat/completed/<project>` on `origin` (the
98
+ authoritative terminal reachability root after archive)
97
99
  - Local: `.oat/projects/local/<project>/`
98
100
  - Archived: `.oat/projects/archived/<project>/`
99
101
 
@@ -108,6 +110,10 @@ Archive sync surfaces:
108
110
  - Remote archive base: `archive.s3Uri` in `.oat/config.json`
109
111
  - Archive sync command: `oat repo archive sync` or `oat repo archive sync <project-name>`
110
112
  - Remote archive snapshot shape: `<archive.s3Uri>/<repo-slug>/projects/YYYYMMDD-<project-name>/`
113
+ - Terminal archive metadata:
114
+ `.oat/projects/archived/<project>/.oat-archive-source.json` binds the
115
+ snapshot to its synced source-ref SHA so a recordless interrupted closeout
116
+ can retry without recreating the active record or checkout.
111
117
  - Summary export target:
112
118
  `<repo>/<archive.summaryExportPath>/YYYYMMDD-<project-name>.md` when
113
119
  configured. The archive report returns this absolute filesystem path, while
@@ -58,14 +58,21 @@ Project scope is used for project workflows and repo-local sync state. User scop
58
58
 
59
59
  ## `.oat/sync/` details
60
60
 
61
- | Path | Purpose |
62
- | ------------------------- | ----------------------------------------------------------------- |
63
- | `.oat/sync/manifest.json` | Records sync-managed provider entries and drift contract state |
64
- | `.oat/sync/config.json` | Project sync behavior, provider settings, and known-stray choices |
61
+ | Path | Purpose |
62
+ | ------------------------- | ------------------------------------------------------------------- |
63
+ | `.oat/sync/manifest.json` | Records sync-managed entries, collection ownership, and drift state |
64
+ | `.oat/sync/config.json` | Project sync behavior, provider settings, and known-stray choices |
65
65
 
66
66
  The equivalent user files are `~/.oat/sync/manifest.json` and
67
67
  `~/.oat/sync/config.json`.
68
68
 
69
+ Manifest version 2 stores `collections` alongside per-entry evidence. Each
70
+ collection record uses scope-relative canonical/provider paths and records
71
+ whether OAT created the exact alias or adopted an existing one. Its inherited
72
+ entries refer back to that collection; the manifest never authorizes deletion
73
+ through the alias target. Writes are atomic, and unchanged manifests from the
74
+ older per-entry schema normalize in memory before the next successful sync.
75
+
69
76
  `config.json` currently includes:
70
77
 
71
78
  - `version`
@@ -181,7 +188,8 @@ Each OAT project lives under:
181
188
  - `.oat/projects/synced/<project>/` for a gitignored nested worktree whose
182
189
  history is published to `refs/oat/projects/<project>`
183
190
  - `.oat/projects/synced/<project>.json` for the tracked discovery record that
184
- identifies the ref, remote, and active or complete status
191
+ identifies the ref, remote, and active transaction state; archived closeout
192
+ deletes this record after terminal durability is verified
185
193
  - `.oat/projects/local/<project>/`
186
194
  - `.oat/projects/archived/<project>/`
187
195
 
@@ -189,16 +197,24 @@ Completion and archive sync behavior:
189
197
 
190
198
  - When archiving is selected, `oat-project-complete` archives locally into
191
199
  `.oat/projects/archived/<project>/`. For a synced project, the snapshot omits
192
- the nested checkout's `.git` pointer and `reviews/`, then completion marks
193
- the tracked record complete, removes the nested worktree, and retains the
194
- project ref so pinned links keep resolving.
200
+ the nested checkout's `.git` pointer and `reviews/`. After required local and
201
+ configured S3 durability succeeds, completion makes
202
+ `refs/oat/completed/<project>` authoritative, removes the nested worktree,
203
+ and deletes the tracked JSON record. Full-SHA links remain reachable through
204
+ the completed ref.
205
+ - Both a completed-only ref and completed plus same-SHA active alias are valid
206
+ terminal shapes. The alias is inert and ignored by list, pull, and open;
207
+ differing SHAs are a hard mismatch with explicit recovery guidance.
195
208
  - When archiving is disabled or declined, completion leaves the synced
196
209
  checkout and ref in place, pushes the finalized artifacts, and exact-path
197
210
  commits the tracked record as complete.
198
- - If `archive.s3SyncOnComplete=true` and `archive.s3Uri` is configured, completion also uploads a dated snapshot such as `<archive.s3Uri>/<repo-slug>/projects/20260401-<project>/`.
211
+ - If `archive.s3SyncOnComplete=true` and `archive.s3Uri` is configured, completion must successfully upload a dated snapshot such as `<archive.s3Uri>/<repo-slug>/projects/20260401-<project>/` before it retires the active record and checkout.
199
212
  - `oat repo archive sync` syncs all repo archived projects down from S3 into `.oat/projects/archived/`.
200
213
  - `oat repo archive sync <project-name>` syncs the latest dated remote snapshot for a single project into `.oat/projects/archived/<project-name>/`.
201
214
  - Default archive sync is non-destructive toward unrelated local-only archive data, but it does replace a local project archive when a newer dated remote snapshot is selected for that same project.
215
+ - `oat project prune` is separate and destructive: for a terminal project it
216
+ removes the completed ref and any matching active alias, while leaving local
217
+ and S3 archive snapshots intact. A ref mismatch blocks deletion.
202
218
 
203
219
  Typical contents:
204
220
 
@@ -28,10 +28,15 @@ rebases it on the fetched project ref, and pushes only that ref. Arrival skills
28
28
  run `oat project pull` before reading artifacts so a record or remote ref can
29
29
  materialize the checkout first.
30
30
 
31
- The tracked JSON record is routing metadata, not a copy of the project. It
32
- identifies the slug, `origin`, ref, status, and archive snapshot when present.
33
- The project ref remains the source of truth for active artifact content and
34
- history.
31
+ The tracked JSON record is active routing metadata, not a copy of the project.
32
+ It identifies the slug, `origin`, active ref, and status. The active project ref
33
+ remains the source of truth for active artifact content and history.
34
+
35
+ Archived closeout transitions terminal identity to
36
+ `refs/oat/completed/<project>`, deletes the active record and checkout, and
37
+ persists source-ref identity in local archive metadata. Arrival and pull flows
38
+ never rematerialize a completed project as active state: the completed ref and
39
+ archive metadata own terminal reachability and retry identity.
35
40
 
36
41
  ## Core artifacts
37
42
 
@@ -63,7 +63,7 @@ launched**, and it records:
63
63
  `gate-target` (adapters may add a more specific diagnostic but never replace
64
64
  or rename these);
65
65
  - the ordered `candidates_considered` before launch (never sorted);
66
- - launch acceptance status (`accepted` or `pre-start-rejected`) and mechanism.
66
+ - launch acceptance status (`accepted` or `blocked-before-start`) and mechanism.
67
67
 
68
68
  Because the launcher constructs the invocation payload itself, this layer does
69
69
  not depend on any child cooperation. A launch is judged consistent only when its
@@ -75,6 +75,20 @@ This layer maps to Dispatch Report V1 and its provenance record; see the
75
75
  [Dispatch Report V1 / producer provenance](dispatch-ceiling.md#dispatch-report-v1-and-producer-provenance)
76
76
  section.
77
77
 
78
+ For project-aware work, OAT persists this native dispatch lineage as one
79
+ generic record per request under the project's `dispatch/` directory. The
80
+ generic snake-case fields remain authoritative. A namespaced `oat` block adds
81
+ only canonical-role identity, proven pre-start rejection, fallback linkage,
82
+ and optional runtime observation. `DispatchReportV1` and the parseable
83
+ `Dispatch:` compatibility stamp keep their existing byte shape.
84
+
85
+ The launcher constructs and redacts the complete payload before calling the
86
+ native host, then records the host's accepted or `blocked-before-start` result
87
+ immediately. Only a provider-wrapper attestation proving that no child started
88
+ can authorize one fresh, exact-target fallback record. Timeout, `BLOCKED`,
89
+ refusal after acceptance, runtime mismatch, missing telemetry, and malformed
90
+ output cannot authorize replacement.
91
+
78
92
  ## Layer 3 — Runtime-observed identity (optional corroboration)
79
93
 
80
94
  Layer 3 is the only layer that reflects what a runtime said about itself, and it
@@ -90,6 +104,95 @@ missing or `not-reported` runtime identity **never invalidates** the
90
104
  launcher-owned configured-invocation evidence in Layer 2. Selected model and
91
105
  effort axes stay exact even when runtime producer identity is not reported.
92
106
 
107
+ ### Provider metadata observation
108
+
109
+ `oat project dispatch record` accepts an optional post-launch observation event
110
+ whose `metadata` is a provider envelope — `provider`, `observedAt`, and
111
+ `entries` — and normalizes it against the record's own immutable configured
112
+ invocation. `entries` may be raw, unmodified provider output. The result is stored under the record's `oat.runtimeObservation`.
113
+
114
+ The channel is metadata-only and capability-gated:
115
+
116
+ | Provider | Observed axes | Notes |
117
+ | -------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
118
+ | Codex | child lineage, role, model, effort | Read from `session_meta` and `turn_context` metadata entries. No captured Codex rollout reports a service tier, so that axis never populates today. |
119
+ | Claude | role, model, effort, service tier; lineage is root-or-child | Read from on-disk `assistant` entry metadata (`attributionAgent`, `effort`, `isSidechain`) plus `message.model` and `message.usage.service_tier`. |
120
+ | Cursor | none | Explicitly `not-reported`; requested values are never copied into it. |
121
+
122
+ Parsers classify entries by their `type` discriminator alone, so a conversation
123
+ entry's body is never read, and only bounded provider identifiers are extracted.
124
+ The Claude parser reads `assistant` entry metadata, which is not the same as
125
+ reading a conversation: it reaches inside `message` by exactly two explicit key
126
+ paths and never spreads, enumerates, or filters that object, so
127
+ `message.content` is never touched rather than being dropped afterwards.
128
+
129
+ Claude reports lineage only as a binary: `root` for a main session and
130
+ `depth-unknown` for a subagent turn. No depth, nesting, or ancestry field exists
131
+ on any captured assistant entry and `parentUuid` links messages within one agent
132
+ rather than agents to each other, so a depth number is reported unknown rather
133
+ than derived.
134
+
135
+ The guarantee is the projection, not caller discipline. Provider parsing happens
136
+ entirely inside the provider module, and what comes back is a flat, closed set
137
+ of neutral facts — lineage, role, model, effort, service tier, correlation —
138
+ whose key names are owned by OAT rather than by the provider. That projection,
139
+ never the raw input, is what the sensitive-content boundary inspects and what is
140
+ compared against the configured invocation. `entries` are never persisted in any
141
+ form.
142
+
143
+ Emitting neutral keys rather than mirroring the provider's own entry shape is
144
+ deliberate. Mirroring a source shape means inheriting its field names, and a
145
+ provider is free to name a field anything: an earlier revision passed the
146
+ provider's shape through and carried `sessionId` and `message` into the output,
147
+ both of which classify as sensitive, which made every real Claude transcript
148
+ unrecordable.
149
+
150
+ A caller may therefore hand over an unmodified rollout or session log: every
151
+ field outside the fact set, including instruction text, conversation bodies,
152
+ working directories, and repository identity, is dropped before the record
153
+ boundary and is never read in the first place. Requiring pre-sanitized input
154
+ instead would push the stripper into every caller, which is a larger leak
155
+ surface than the one it removes.
156
+
157
+ Comparison covers only the axes both sides report, and `comparedAxes` records
158
+ exactly which ones. This qualifier is load-bearing: `matching` means "every axis
159
+ in `comparedAxes` agreed", never "the configured invocation was confirmed". An
160
+ observation reporting only a service tier can be `matching` while model, role,
161
+ and effort are all unknown, so read `comparedAxes` — or the `on role+model`
162
+ qualifier in human output — before treating a match as corroboration. With no
163
+ comparable axis the match is `not-comparable`, because silence is not agreement.
164
+
165
+ An axis a provider genuinely does not expose (`not-exposed`) is excluded rather
166
+ than compared as a literal. That value is reserved for a truly absent axis: an
167
+ axis a provider does report is recorded with its observed value, and an axis it
168
+ simply did not mention on a given run is left unreported. An axis the configured invocation names under two equally
169
+ authoritative spellings — a canonical role name and its materialized native
170
+ selector — matches either.
171
+
172
+ `match` and `comparedAxes` are always derived at the durable-write boundary from
173
+ the record's own configured invocation. A caller that submits a finished
174
+ observation cannot assert its own verdict, and an observation naming a provider
175
+ the record does not use is refused.
176
+
177
+ Request correlation is verified only when the provider declares one: a session
178
+ that names a different request is declined, and a session that declares none is
179
+ attributed to the request the caller paired it with. In practice neither
180
+ provider declares an OAT request id today — no captured Codex rollout carries
181
+ `session_meta.request_id`, and Claude's own `requestId` is a per-turn API
182
+ identifier rather than a dispatch request — so correlation is caller-asserted
183
+ for both, and the declared-correlation check is a forward-compatible guard.
184
+
185
+ A parse failure, a declined request correlation, an unsupported provider, or an
186
+ envelope too large to project all produce `not-reported`. A `runtime-observation`
187
+ event carrying neither `metadata` nor `observation` is a malformed event and is
188
+ rejected by the schema rather than degraded. None of them copies a requested
189
+ argument or a materialized pin into observed state, and `mismatching` is
190
+ evidence only: it authorizes no replacement, retry, or fallback, and leaves
191
+ `launch_status`, `child_outcome`, and every configured control untouched. The
192
+ `--json` result reports `runtimeIdentity.configured` and
193
+ `runtimeIdentity.observed` as separate objects that are never merged into one
194
+ "effective" identity.
195
+
93
196
  ## How the smoke runner consumes these layers
94
197
 
95
198
  The smoke runner's evidence pipeline reads all three layers and asserts only on
@@ -108,6 +211,12 @@ collection. The collector then flows the evidence through three stages:
108
211
  artifacts, carrying `reported` / `not-reported` runtime status without
109
212
  letting a missing Layer 3 fail a Layer 2 assertion.
110
213
 
214
+ The collector projects `runtimeObservation` the same way: it reads committed
215
+ records and never launches a provider, so an observation appears in a bundle
216
+ only because a launcher-owned record already carried one. A partially filled
217
+ observation normalizes to `not-reported` rather than being completed from any
218
+ other layer.
219
+
111
220
  For how to run this end to end and when to refresh the fixture, see
112
221
  [Smoke testing](../../contributing/smoke-testing.md).
113
222
 
@@ -32,6 +32,9 @@ and updates project state.
32
32
  third tier.
33
33
  - **Parallelism:** plan-declared phases may run concurrently in separate
34
34
  worktrees. Tasks inside one phase remain serial.
35
+ - **Dispatch evidence:** project-aware launches persist one generic request
36
+ record plus namespaced OAT native dispatch lineage without changing native
37
+ model or provider selection.
35
38
 
36
39
  Tasks execute serially in one worktree for the phase.
37
40
 
@@ -69,6 +72,16 @@ The root:
69
72
  The root does not implement phase tasks while an accepted phase launch owns
70
73
  that scope.
71
74
 
75
+ Before a native launch, the root constructs and redacts the complete generic
76
+ dispatch record and OAT role event. Immediately after the host returns, it runs
77
+ `oat project dispatch record --project <project-path> --event-file - --json`
78
+ with the accepted or `blocked-before-start` result. An accepted launch closes
79
+ replacement. Only an explicit rejection proving no child started permits one
80
+ fresh request that preserves the exact target and controls and is labeled as
81
+ an approximation. Timeout, `BLOCKED`, refusal after acceptance, runtime
82
+ mismatch, missing telemetry, and malformed output are terminal evidence, not
83
+ fallback triggers.
84
+
72
85
  ### Phase implementer
73
86
 
74
87
  The phase implementer receives one `Phase Scope`, reads the relevant artifacts
@@ -84,14 +84,26 @@ On completion, OAT treats archive handling as an explicit closeout choice:
84
84
  - For a synced project, closeout first finalizes the project artifacts and
85
85
  pushes them to `refs/oat/projects/<project>`. Archive then requires a clean,
86
86
  fully pushed checkout; copies it without the `.git` pointer or `reviews/`;
87
- marks the tracked record complete; commits the record and configured durable
88
- exports on the parent branch; removes the nested checkout; and retains the
89
- ref. This order keeps SHA-pinned PR links valid after completion.
90
- - If `.oat/config.json` enables `archive.s3SyncOnComplete` and sets `archive.s3Uri`, completion also attempts an S3 upload for a dated snapshot such as `<archive.s3Uri>/<repo-slug>/projects/20260401-<project>/`.
87
+ seals configured durable exports; transitions terminal reachability to
88
+ `refs/oat/completed/<project>`; removes the nested checkout; and deletes the
89
+ tracked JSON record in the parent lifecycle commit. This order keeps
90
+ SHA-pinned PR links valid without leaving active discovery state behind.
91
+ - A completed-only ref and a completed ref plus same-SHA active alias are both
92
+ valid terminal shapes. The active alias is inert: list omits it and pull/open
93
+ reject it. Differing SHAs are a hard mismatch with recovery guidance.
94
+ - If `.oat/config.json` enables `archive.s3SyncOnComplete` and sets `archive.s3Uri`, completion requires the S3 upload of a dated snapshot such as `<archive.s3Uri>/<repo-slug>/projects/20260401-<project>/` to succeed before terminal record/ref cleanup. A failed configured upload leaves retry identity intact and does not claim closeout.
91
95
  - If `.oat/config.json` sets `archive.awsProfile` and/or `archive.awsRegion`, those values are forwarded to every `aws` invocation triggered by completion (preflight checks + `aws s3 sync`) and override any ambient shell `AWS_PROFILE` / `AWS_DEFAULT_PROFILE` / `AWS_REGION` / `AWS_DEFAULT_REGION` values. The repo's archive-scoped credentials are treated as deliberate intent so users don't have to unset shell env vars before running completion. See [`config-and-local-state.md`](../../cli-utilities/config-and-local-state.md) for the full precedence chain.
92
96
  - If `.oat/config.json` sets `archive.summaryExportPath`, completion copies `summary.md` to `<archive.summaryExportPath>/20260401-<project>.md`.
93
- - Missing or unusable AWS CLI configuration produces warnings during completion instead of blocking closeout.
97
+ - Missing or unusable AWS CLI configuration blocks synced terminal cleanup when S3 durability is configured; when S3 sync is not configured, it is not part of the durability requirement.
94
98
  - `oat repo archive sync` can later sync all archived projects, or one named archived project, back down from S3; it selects the latest dated remote snapshot and materializes it into the local bare archive tree.
99
+ - Persisted archive metadata binds the snapshot to the source-ref SHA, allowing
100
+ an interrupted closeout or legacy complete record to resume without
101
+ rematerializing an active project. Dashboard and list surfaces omit fully
102
+ retired projects and report precise cleanup or ref-mismatch diagnoses for
103
+ remaining legacy state.
104
+ - `oat project links` continues to render full-SHA links through the completed
105
+ ref. `oat project prune` is a separate destructive choice that removes
106
+ terminal ref reachability but preserves local and S3 archive snapshots.
95
107
 
96
108
  ### Phase status: `pr_open`
97
109
 
@@ -76,12 +76,14 @@ sequenceDiagram
76
76
  participant Root as Project root
77
77
  participant Adapter as Project adapter
78
78
  participant Engine as Dispatch engine
79
+ participant Journal as Project dispatch journal
79
80
  participant Phase as Phase implementer
80
81
 
81
82
  Root->>Adapter: Phase + lifecycle authority
82
83
  Adapter->>Engine: Provider-neutral dispatch request
83
84
  Engine-->>Root: Exact route + request ID
84
85
  Root->>Phase: Phase Scope
86
+ Root->>Journal: Accepted or blocked-before-start record
85
87
  Phase-->>Root: Phase report + commits
86
88
  ```
87
89
 
@@ -118,6 +120,22 @@ original `request_id`.
118
120
  This preserves continuity without allowing replacement after an accepted failed
119
121
  launch.
120
122
 
123
+ ## Native Dispatch Lineage
124
+
125
+ The launcher constructs and redacts the complete generic record plus the
126
+ namespaced OAT role event before it calls the native host. Immediately after
127
+ the host returns, the project adapter validates and persists the accepted or
128
+ `blocked-before-start` state with `oat project dispatch record`. The command is
129
+ a recorder only; it never launches a provider or changes selection authority.
130
+
131
+ An accepted handle owns the scope and closes replacement. One fresh fallback
132
+ record is legal only when the native wrapper proves no child started, links the
133
+ trigger and fallback request IDs, preserves the exact provider/model/effort/
134
+ route/authority controls, resolves canonical role identity, and labels the
135
+ fresh child as an approximation. Timeout, `BLOCKED`, refusal after acceptance,
136
+ runtime mismatch, missing telemetry, interruption, and malformed output never
137
+ authorize a fallback.
138
+
121
139
  ## Optional Third Tier
122
140
 
123
141
  Nested work is appropriate when the work itself benefits:
@@ -25,6 +25,14 @@ the nested checkout. When the selected project is a coordination parent, pull
25
25
  also discovers and pulls its child projects by default. Pass `--no-children`
26
26
  only when you intentionally want the selected project alone.
27
27
 
28
+ Completed projects are not adoption candidates. Their authoritative ref is
29
+ `refs/oat/completed/<project>`; both `pull` and `open` return a terminal
30
+ diagnosis instead of recreating an archived checkout or record. A same-SHA
31
+ active ref may remain as an inert alias and is ignored. Differing active and
32
+ completed SHAs require repair before lifecycle work continues. The same guard
33
+ applies to coordination children: a terminal child discovered while pulling
34
+ its parent is reported and skipped rather than adopted.
35
+
28
36
  After adoption, set or open the project through the normal lifecycle command
29
37
  you are using. Arrival-aware project skills pull before reading its artifacts.
30
38
 
@@ -47,17 +55,19 @@ you are using. Arrival-aware project skills pull before reading its artifacts.
47
55
  project ref during sync operations such as `pull`; do not expect a generic
48
56
  clone or `git fetch` to materialize the checkout.
49
57
 
50
- ## Why retained refs remain durable
58
+ ## Why completed refs remain durable
51
59
 
52
- `refs/oat/projects/<project>` is a real Git ref locally and on `origin`. The
60
+ `refs/oat/completed/<project>` is a real Git ref locally and on `origin`. The
53
61
  objects reachable from it remain garbage-collection roots even when no nested
54
62
  checkout exists. Branch pruning, remote-tracking-ref pruning, and
55
- `git worktree prune` do not delete the project ref. Completion deliberately
56
- retains it so pinned PR links remain valid.
63
+ `git worktree prune` do not delete the completed ref. Completion deliberately
64
+ retains this terminal reachability root so pinned PR links remain valid.
57
65
 
58
- Only the explicit destructive operation `oat project prune` removes the local
59
- and remote project refs. Treat it as permanent project-history deletion and
60
- review its warnings before using `--force`.
66
+ Only the explicit destructive operation `oat project prune` removes the
67
+ completed ref and any matching active alias. Treat that as permanent Git
68
+ project-history reachability deletion and review its warnings before using
69
+ `--force`. Prune does not remove durable local or S3 archive snapshots, and it
70
+ refuses to delete either ref when their SHAs differ.
61
71
 
62
72
  ## Archive contents
63
73
 
@@ -65,8 +75,15 @@ When archive is selected, completion copies a synced project into
65
75
  `.oat/projects/archived/<project>/` without the nested checkout's `.git`
66
76
  pointer or `reviews/`. S3 snapshots also omit `pr/`, following the existing
67
77
  archive policy. When archive is disabled or declined, the active synced
68
- checkout remains in place. Both paths retain the project ref and commit the
69
- tracked record as `complete`; only the archive path records an archive snapshot.
78
+ checkout remains in place. Successful archived closeout instead uploads the S3
79
+ snapshot first when configured, makes the completed ref authoritative, removes
80
+ the checkout, and deletes the tracked JSON record. The local archive metadata
81
+ retains the source-ref identity needed for recordless retries and later S3
82
+ restore without recreating active state.
83
+
84
+ If closeout is interrupted after terminal archive identity exists, rerun
85
+ completion so it can resume from the completed ref and archive metadata. Do not
86
+ use `pull` or `open` to recreate an active record or checkout.
70
87
 
71
88
  ## Related
72
89
 
@@ -76,9 +76,11 @@ Both completion orderings are supported:
76
76
 
77
77
  An open PR is not a completion blocker. When completion archives project
78
78
  artifacts, the archive-aware flow regenerates and synchronizes the open PR body
79
- so its artifact links remain valid. Synced completion retains the project ref,
80
- which keeps its SHA-pinned links resolvable after the nested checkout is
81
- removed.
79
+ so its artifact links remain valid. Synced completion makes
80
+ `refs/oat/completed/<project>` authoritative at the verified source SHA. A
81
+ same-SHA `refs/oat/projects/<project>` ref may remain only as an inert alias;
82
+ the completed ref keeps the SHA-pinned links resolvable after the nested
83
+ checkout and tracked record are removed.
82
84
 
83
85
  ## Reference artifacts
84
86
 
@@ -6,17 +6,21 @@ description: Understand synced-project records, SHA-pinned artifact links, and t
6
6
  # Reviewing OAT PRs
7
7
 
8
8
  A PR for a synced OAT project contains the implementation diff without placing
9
- the agent-facing project tree on the feature branch. Reviewers get durable
10
- project context through a small tracked record and a generated links block in
11
- the PR body.
9
+ the agent-facing project tree on the feature branch. While the project is
10
+ active, reviewers discover its context through a small tracked record and a
11
+ generated links block in the PR body.
12
12
 
13
13
  ## The synced record
14
14
 
15
- `.oat/projects/synced/<project>.json` is tracked on the feature branch. It
16
- names the project slug, `origin`, `refs/oat/projects/<project>`, whether the
17
- project is active or complete, and the archive snapshot when completion has
18
- created one. It is discovery and lifecycle metadata; the project artifacts
19
- themselves live on the project ref.
15
+ `.oat/projects/synced/<project>.json` is tracked on the feature branch while
16
+ the project is active. It names the project slug, `origin`, and
17
+ `refs/oat/projects/<project>`. The record and active ref are discovery and
18
+ lifecycle metadata; the project artifacts themselves live on the project ref.
19
+
20
+ Successful archived closeout deletes the tracked record and makes
21
+ `refs/oat/completed/<project>` the authoritative terminal reachability root.
22
+ The PR's existing SHA-pinned artifact links remain valid through that completed
23
+ ref even after the active checkout and record are gone.
20
24
 
21
25
  ## The artifact links block
22
26
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.2.50",
3
- "docs-config": "0.2.50",
4
- "docs-theme": "0.2.50",
5
- "docs-transforms": "0.2.50"
2
+ "cli": "0.2.52",
3
+ "docs-config": "0.2.52",
4
+ "docs-theme": "0.2.52",
5
+ "docs-transforms": "0.2.52"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-dispatch-subagents
3
- version: 1.2.3
3
+ version: 1.2.4
4
4
  description: Use when an OAT skill or workflow needs provider-neutral selection, launch, recovery, or evidence for bounded subagent work without project lifecycle policy.
5
5
  disable-model-invocation: true
6
6
  user-invocable: false
@@ -129,6 +129,62 @@ decomposition.
129
129
  The optional policy and ceiling are already-resolved inputs. Do not infer where
130
130
  they came from or resolve project state to obtain them.
131
131
 
132
+ ## Native Dispatch Lineage
133
+
134
+ For a project-aware dispatch, the calling workflow records native dispatch
135
+ lineage through the project adapter; this provider-neutral engine never writes
136
+ project state itself.
137
+
138
+ 1. Construct the complete generic record and namespaced OAT event, redact it,
139
+ and validate sensitive-content exclusion before the native host call or
140
+ launch. Construction is in memory; do not claim a launch result yet.
141
+ 2. Invoke the exact native target once. Immediately after the host returns,
142
+ record either `launch_status: accepted` or
143
+ `launch_status: blocked-before-start` in the generic dispatch record. A
144
+ blocked result also requires the provider-wrapper attestation
145
+ `provesNoChildStarted: true`.
146
+ 3. Preserve the exact target and controls: provider, model, effort, reasoning
147
+ mode, service tier, route, authority, sandbox/tool payload, deadline, and
148
+ retry limit. Namespaced evidence cannot redefine any generic field.
149
+ 4. Only a proven pre-start rejection permits one fresh fallback record; that
150
+ one fallback requires `provesNoChildStarted: true`. It preserves the exact
151
+ target and controls, links both request IDs,
152
+ includes resolved canonical role identity, and says `approximation: true`.
153
+ 5. Timeout, `BLOCKED`, refusal after acceptance, runtime mismatch, missing
154
+ telemetry, malformed output, and interruption never authorize fallback or
155
+ replacement. Continue only through the accepted handle or stop.
156
+
157
+ Pre-start native-selection rejection is a closed event set, disjoint from
158
+ terminal child-outcome codes. Use exactly one of `native-role-unavailable`,
159
+ `native-target-unavailable`, `native-selector-unsupported`,
160
+ `native-catalog-unsatisfying`, `capability-unresolved-or-unsupported`,
161
+ `wrapper-payload-rejected`, or `wrapper-launch-refused`. The recorder rejects
162
+ every other code, and it names the prohibited outcome family when a terminal
163
+ code such as `timeout` is submitted. A fallback additionally requires the
164
+ trigger record to carry resolved canonical role evidence, and the fallback's
165
+ role evidence must equal it exactly.
166
+
167
+ `wrapper-launch-refused` covers only conditions in which the launch surface
168
+ declined the request and no child process was ever created: a rejected or
169
+ unsupported payload, a refused authorization scope, an unavailable or
170
+ unregistered target, or a wrapper precondition failure raised before spawn. Any
171
+ condition in which a child process was created — including spawn success
172
+ followed by immediate exit, a crash, a broken handle, or a lost transport — is a
173
+ terminal outcome, not a pre-start rejection, and must be recorded through
174
+ `child_outcome`. If you cannot prove the child never existed, you cannot set
175
+ `provesNoChildStarted: true`, and no fallback is authorized.
176
+
177
+ Project-aware callers pass the validated record and event on standard input:
178
+
179
+ ```bash
180
+ oat project dispatch record \
181
+ --project "$PROJECT_PATH" \
182
+ --event-file - \
183
+ --json
184
+ ```
185
+
186
+ The command validates and persists evidence only. It never launches a provider.
187
+
132
188
  ## Capability and Authorization
133
189
 
134
190
  Classify delegation before launch:
@@ -191,7 +191,9 @@ current nested dispatcher. It does not imply a stable enum or authorize
191
191
  reconstruction of a materialized lifecycle variant.
192
192
 
193
193
  `role_selector` is the exact provider or harness agent-type selector, when that
194
- surface exists. Preserve opaque selectors byte-for-byte.
194
+ surface exists. Preserve opaque selectors byte-for-byte, within the
195
+ 256-character identifier bound described under
196
+ [Size and Content Bounds](#size-and-content-bounds).
195
197
 
196
198
  Use the stable selection reasons `native-catalog`,
197
199
  `native-catalog-unsatisfying`, `pre-start-rejection`, `inherit`, and
@@ -257,3 +259,77 @@ the interpreted service tier when known.
257
259
  A service tier never changes `model_class_floor` or `floor_satisfaction`.
258
260
  Unknown tier semantics must be recorded as a diagnostic and may block a
259
261
  consequential route.
262
+
263
+ ## Size and Content Bounds
264
+
265
+ A dispatch record is evidence, not a document. Validation enforces these bounds
266
+ and fails closed; know them before authoring a record rather than discovering
267
+ them at a durable write.
268
+
269
+ Caller-authored text, by kind:
270
+
271
+ - 256 characters — identifiers, names, selectors, routes, and statuses.
272
+ - 512 characters — a single explanatory field such as `classification_reason`
273
+ or one `escalate_when` entry.
274
+ - 1024 characters — the longest free-form fields: `objective`,
275
+ `expected_output`, and `verification_evidence`.
276
+
277
+ Six container fields carry short references and identifiers, never narrative
278
+ text: `payload`, `candidates_considered`, `configured_invocation_evidence`,
279
+ `continuation_events`, `diagnostics`, and `escalate_when`. Each is validated as
280
+ a closed JSON projection bounded on four independent axes, so no one axis can
281
+ be traded against another:
282
+
283
+ - nesting depth at most 4 levels;
284
+ - each string at most 512 characters;
285
+ - at most 512 values per field, which closes chunking one body into many short
286
+ strings; and
287
+ - at most 16 KiB serialized per field.
288
+
289
+ The whole record is additionally capped at 64 KiB, measured as the **published**
290
+ bytes — the pretty-printed JSON plus trailing newline that is actually written
291
+ to the journal, not compact `JSON.stringify` output. The two differ by roughly a
292
+ tenth on a nested record, so size against the published form. Per-field
293
+ projection limits above remain compact measurements, because they bound the
294
+ shape of a field rather than the size of a file. A realistically rich record
295
+ measures a few kilobytes, so these limits bind prompt bodies, transcripts, and
296
+ chunked logs rather than legitimate evidence.
297
+
298
+ Keys are classified against credential, token, prompt/message, transcript, and
299
+ role-content families after Unicode folding, and a spelling the normalizer
300
+ cannot fully account for is treated as sensitive rather than classified on a
301
+ partial token. Store references, identifiers, and digests; never role content, message bodies,
302
+ or credentials.
303
+
304
+ Absolute filesystem paths are handled by field class, because the journal is
305
+ committed and a home path in it is permanent:
306
+
307
+ - **Identity and control fields reject them.** `caller`, `scope`, every
308
+ selector, `selected_route`, the `guidance_*` fields, `authority`,
309
+ `authorization_scope`, `catalog_snapshot`, `candidates_considered` and
310
+ `fallback` refuse a record containing an absolute path, including
311
+ colon-prefixed forms such as `cwd:/Users/alice`. A path is never a legitimate
312
+ identifier there, so ambiguity resolves to rejection. Pass a repository-relative
313
+ path or a redacted form like `<user>/agents/<role>.md`.
314
+ - **Prose and nested evidence redact them best-effort.** `objective`,
315
+ `expected_output`, `verification_evidence`, `classification_reason`,
316
+ `escalate_when`, and the bounded container fields replace a detected path with
317
+ `<redacted-path>`. This is deliberately conservative rather than complete: it
318
+ skips candidates inside an `http`/`https` URL and candidates ending in `/`, so
319
+ a URL route (`?next=/dashboard`) and a regex literal (`/foo/bar/`) survive
320
+ intact. Colon-prefixed forms also survive. Corrupting a legitimate value is
321
+ worse than leaving a path in prose, so do not rely on this to sanitize
322
+ caller-authored text — redact before submitting.
323
+
324
+ Optional runtime observation is metadata-only and subject to the same
325
+ identifier bound, which admits letters, digits, `.`, `_`, and `-` only, so no
326
+ observation value can carry a path or a URL. Its `match` and `comparedAxes` are
327
+ always derived from the record's configured invocation rather than supplied by
328
+ the caller, and `matching` covers only the axes listed in `comparedAxes`.
329
+ It records what a provider reported about its own child and never restates the
330
+ request. An axis the provider did not report is simply absent from the
331
+ observation; an absent or unparseable observation is `not-reported` as a whole;
332
+ and `not-exposed` is reserved vocabulary for an axis a provider genuinely has
333
+ no selectable control for, with no producer today. None of the three is ever
334
+ filled in from `model_selector`, `effort_selector`, `service_tier_selector`, or
335
+ `role_selector`.