@wazir-dev/cli 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/docs/research/2026-03-20-agents/a18fb002157904af5.txt +187 -0
  3. package/docs/research/2026-03-20-agents/a1d0ac79ac2f11e6f.txt +2 -0
  4. package/docs/research/2026-03-20-agents/a324079de037abd7c.txt +198 -0
  5. package/docs/research/2026-03-20-agents/a357586bccfafb0e5.txt +256 -0
  6. package/docs/research/2026-03-20-agents/a4365394e4d753105.txt +137 -0
  7. package/docs/research/2026-03-20-agents/a492af28bc52d3613.txt +136 -0
  8. package/docs/research/2026-03-20-agents/a4984db0b6a8eee07.txt +124 -0
  9. package/docs/research/2026-03-20-agents/a5b30e59d34bbb062.txt +214 -0
  10. package/docs/research/2026-03-20-agents/a5cf7829dab911586.txt +165 -0
  11. package/docs/research/2026-03-20-agents/a607157c30dd97c9e.txt +96 -0
  12. package/docs/research/2026-03-20-agents/a60b68b1e19d1e16b.txt +115 -0
  13. package/docs/research/2026-03-20-agents/a722af01c5594aba0.txt +166 -0
  14. package/docs/research/2026-03-20-agents/a787bdc516faa5829.txt +181 -0
  15. package/docs/research/2026-03-20-agents/a7c46d1bba1056ed2.txt +132 -0
  16. package/docs/research/2026-03-20-agents/a7e5abbab2b281a0d.txt +100 -0
  17. package/docs/research/2026-03-20-agents/a8dbadc66cd0d7d5a.txt +95 -0
  18. package/docs/research/2026-03-20-agents/a904d9f45d6b86a6d.txt +75 -0
  19. package/docs/research/2026-03-20-agents/a927659a942ee7f60.txt +102 -0
  20. package/docs/research/2026-03-20-agents/a962cb569191f7583.txt +125 -0
  21. package/docs/research/2026-03-20-agents/aab6decea538aac41.txt +148 -0
  22. package/docs/research/2026-03-20-agents/abd58b853dd938a1b.txt +295 -0
  23. package/docs/research/2026-03-20-agents/ac009da573eff7f65.txt +100 -0
  24. package/docs/research/2026-03-20-agents/ac1bc783364405e5f.txt +190 -0
  25. package/docs/research/2026-03-20-agents/aca5e2b57fde152a0.txt +132 -0
  26. package/docs/research/2026-03-20-agents/ad849b8c0a7e95b8b.txt +176 -0
  27. package/docs/research/2026-03-20-agents/adc2b12a4da32c962.txt +258 -0
  28. package/docs/research/2026-03-20-agents/af97caaaa9a80e4cb.txt +146 -0
  29. package/docs/research/2026-03-20-agents/afc5faceee368b3ca.txt +111 -0
  30. package/docs/research/2026-03-20-agents/afdb282d866e3c1e4.txt +164 -0
  31. package/docs/research/2026-03-20-agents/afe9d1f61c02b1e8d.txt +299 -0
  32. package/docs/research/2026-03-20-agents/b4hmkwril.txt +1856 -0
  33. package/docs/research/2026-03-20-agents/b80ptk89g.txt +1856 -0
  34. package/docs/research/2026-03-20-agents/bf54s1jss.txt +1150 -0
  35. package/docs/research/2026-03-20-agents/bhd6kq2kx.txt +1856 -0
  36. package/docs/research/2026-03-20-agents/bmb2fodyr.txt +988 -0
  37. package/docs/research/2026-03-20-agents/bmmsrij8i.txt +826 -0
  38. package/docs/research/2026-03-20-agents/bn4t2ywpu.txt +2175 -0
  39. package/docs/research/2026-03-20-agents/bu22t9f1z.txt +0 -0
  40. package/docs/research/2026-03-20-agents/bwvl98v2p.txt +738 -0
  41. package/docs/research/2026-03-20-agents/psych-a3697a7fd06eb64fd.txt +135 -0
  42. package/docs/research/2026-03-20-agents/psych-a37776fabc870feae.txt +123 -0
  43. package/docs/research/2026-03-20-agents/psych-a5b1fe05c0589efaf.txt +2 -0
  44. package/docs/research/2026-03-20-agents/psych-a95c15b1f29424435.txt +76 -0
  45. package/docs/research/2026-03-20-agents/psych-a9c26f4d9172dde7c.txt +2 -0
  46. package/docs/research/2026-03-20-agents/psych-aa19c69f0ca2c5ad3.txt +2 -0
  47. package/docs/research/2026-03-20-agents/psych-aa4e4cb70e1be5ecb.txt +95 -0
  48. package/docs/research/2026-03-20-agents/psych-ab5b302f26a554663.txt +102 -0
  49. package/docs/research/2026-03-20-deep-research-complete.md +101 -0
  50. package/docs/research/2026-03-20-deep-research-status.md +38 -0
  51. package/docs/research/2026-03-20-enforcement-research.md +107 -0
  52. package/expertise/composition-map.yaml +27 -8
  53. package/expertise/digests/reviewer/ai-coding-digest.md +83 -0
  54. package/expertise/digests/reviewer/architectural-thinking-digest.md +63 -0
  55. package/expertise/digests/reviewer/architecture-antipatterns-digest.md +49 -0
  56. package/expertise/digests/reviewer/code-smells-digest.md +53 -0
  57. package/expertise/digests/reviewer/coupling-cohesion-digest.md +54 -0
  58. package/expertise/digests/reviewer/ddd-digest.md +60 -0
  59. package/expertise/digests/reviewer/dependency-risk-digest.md +40 -0
  60. package/expertise/digests/reviewer/error-handling-digest.md +55 -0
  61. package/expertise/digests/reviewer/review-methodology-digest.md +49 -0
  62. package/exports/hosts/claude/.claude/commands/learn.md +61 -8
  63. package/exports/hosts/claude/.claude/settings.json +7 -6
  64. package/exports/hosts/claude/export.manifest.json +6 -3
  65. package/exports/hosts/claude/host-package.json +3 -0
  66. package/exports/hosts/codex/export.manifest.json +6 -3
  67. package/exports/hosts/codex/host-package.json +3 -0
  68. package/exports/hosts/cursor/.cursor/hooks.json +6 -6
  69. package/exports/hosts/cursor/export.manifest.json +6 -3
  70. package/exports/hosts/cursor/host-package.json +3 -0
  71. package/exports/hosts/gemini/export.manifest.json +6 -3
  72. package/exports/hosts/gemini/host-package.json +3 -0
  73. package/hooks/definitions/pretooluse_dispatcher.yaml +26 -0
  74. package/hooks/definitions/pretooluse_pipeline_guard.yaml +22 -0
  75. package/hooks/definitions/stop_pipeline_gate.yaml +22 -0
  76. package/hooks/hooks.json +7 -6
  77. package/hooks/pretooluse-dispatcher +84 -0
  78. package/hooks/pretooluse-pipeline-guard +9 -0
  79. package/hooks/stop-pipeline-gate +9 -0
  80. package/package.json +2 -2
  81. package/schemas/decision.schema.json +15 -0
  82. package/schemas/hook.schema.json +4 -1
  83. package/skills/TEMPLATE-3-ZONE.md +160 -0
  84. package/skills/brainstorming/SKILL.md +127 -23
  85. package/skills/clarifier/SKILL.md +175 -18
  86. package/skills/claude-cli/SKILL.md +91 -12
  87. package/skills/codex-cli/SKILL.md +91 -12
  88. package/skills/debugging/SKILL.md +133 -38
  89. package/skills/design/SKILL.md +173 -37
  90. package/skills/dispatching-parallel-agents/SKILL.md +129 -31
  91. package/skills/executing-plans/SKILL.md +113 -25
  92. package/skills/executor/SKILL.md +185 -21
  93. package/skills/finishing-a-development-branch/SKILL.md +107 -18
  94. package/skills/gemini-cli/SKILL.md +91 -12
  95. package/skills/humanize/SKILL.md +92 -13
  96. package/skills/init-pipeline/SKILL.md +90 -17
  97. package/skills/prepare-next/SKILL.md +93 -24
  98. package/skills/receiving-code-review/SKILL.md +90 -16
  99. package/skills/requesting-code-review/SKILL.md +100 -24
  100. package/skills/requesting-code-review/code-reviewer.md +29 -17
  101. package/skills/reviewer/SKILL.md +190 -50
  102. package/skills/run-audit/SKILL.md +92 -15
  103. package/skills/scan-project/SKILL.md +93 -14
  104. package/skills/self-audit/SKILL.md +113 -39
  105. package/skills/skill-research/SKILL.md +94 -7
  106. package/skills/subagent-driven-development/SKILL.md +129 -30
  107. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +30 -2
  108. package/skills/subagent-driven-development/implementer-prompt.md +40 -27
  109. package/skills/subagent-driven-development/spec-reviewer-prompt.md +25 -12
  110. package/skills/tdd/SKILL.md +125 -20
  111. package/skills/using-git-worktrees/SKILL.md +118 -28
  112. package/skills/using-skills/SKILL.md +116 -29
  113. package/skills/verification/SKILL.md +127 -22
  114. package/skills/wazir/SKILL.md +517 -153
  115. package/skills/writing-plans/SKILL.md +134 -28
  116. package/skills/writing-skills/SKILL.md +91 -13
  117. package/skills/writing-skills/anthropic-best-practices.md +104 -64
  118. package/skills/writing-skills/persuasion-principles.md +100 -34
  119. package/tooling/src/capture/command.js +29 -1
  120. package/tooling/src/capture/decision.js +40 -0
  121. package/tooling/src/capture/store.js +1 -0
  122. package/tooling/src/config/depth-table.js +60 -0
  123. package/tooling/src/export/compiler.js +7 -8
  124. package/tooling/src/guards/guardrail-functions.js +131 -0
  125. package/tooling/src/guards/phase-prerequisite-guard.js +39 -3
  126. package/tooling/src/hooks/pretooluse-dispatcher.js +300 -0
  127. package/tooling/src/hooks/pretooluse-pipeline-guard.js +141 -0
  128. package/tooling/src/hooks/stop-pipeline-gate.js +92 -0
  129. package/tooling/src/learn/pipeline.js +177 -0
  130. package/tooling/src/state/db.js +251 -2
  131. package/tooling/src/state/pipeline-state.js +262 -0
  132. package/wazir.manifest.yaml +3 -0
  133. package/workflows/learn.md +61 -8
@@ -0,0 +1,2175 @@
1
+ # Symphony Service Specification
2
+
3
+ Status: Draft v1 (language-agnostic)
4
+
5
+ Purpose: Define a service that orchestrates coding agents to get project work done.
6
+
7
+ ## 1. Problem Statement
8
+
9
+ Symphony is a long-running automation service that continuously reads work from an issue tracker
10
+ (Linear in this specification version), creates an isolated workspace for each issue, and runs a
11
+ coding agent session for that issue inside the workspace.
12
+
13
+ The service solves four operational problems:
14
+
15
+ - It turns issue execution into a repeatable daemon workflow instead of manual scripts.
16
+ - It isolates agent execution in per-issue workspaces so agent commands run only inside per-issue
17
+ workspace directories.
18
+ - It keeps the workflow policy in-repo (`WORKFLOW.md`) so teams version the agent prompt and runtime
19
+ settings with their code.
20
+ - It provides enough observability to operate and debug multiple concurrent agent runs.
21
+
22
+ Implementations are expected to document their trust and safety posture explicitly. This
23
+ specification does not require a single approval, sandbox, or operator-confirmation policy; some
24
+ implementations may target trusted environments with a high-trust configuration, while others may
25
+ require stricter approvals or sandboxing.
26
+
27
+ Important boundary:
28
+
29
+ - Symphony is a scheduler/runner and tracker reader.
30
+ - Ticket writes (state transitions, comments, PR links) are typically performed by the coding agent
31
+ using tools available in the workflow/runtime environment.
32
+ - A successful run may end at a workflow-defined handoff state (for example `Human Review`), not
33
+ necessarily `Done`.
34
+
35
+ ## 2. Goals and Non-Goals
36
+
37
+ ### 2.1 Goals
38
+
39
+ - Poll the issue tracker on a fixed cadence and dispatch work with bounded concurrency.
40
+ - Maintain a single authoritative orchestrator state for dispatch, retries, and reconciliation.
41
+ - Create deterministic per-issue workspaces and preserve them across runs.
42
+ - Stop active runs when issue state changes make them ineligible.
43
+ - Recover from transient failures with exponential backoff.
44
+ - Load runtime behavior from a repository-owned `WORKFLOW.md` contract.
45
+ - Expose operator-visible observability (at minimum structured logs).
46
+ - Support restart recovery without requiring a persistent database.
47
+
48
+ ### 2.2 Non-Goals
49
+
50
+ - Rich web UI or multi-tenant control plane.
51
+ - Prescribing a specific dashboard or terminal UI implementation.
52
+ - General-purpose workflow engine or distributed job scheduler.
53
+ - Built-in business logic for how to edit tickets, PRs, or comments. (That logic lives in the
54
+ workflow prompt and agent tooling.)
55
+ - Mandating strong sandbox controls beyond what the coding agent and host OS provide.
56
+ - Mandating a single default approval, sandbox, or operator-confirmation posture for all
57
+ implementations.
58
+
59
+ ## 3. System Overview
60
+
61
+ ### 3.1 Main Components
62
+
63
+ 1. `Workflow Loader`
64
+ - Reads `WORKFLOW.md`.
65
+ - Parses YAML front matter and prompt body.
66
+ - Returns `{config, prompt_template}`.
67
+
68
+ 2. `Config Layer`
69
+ - Exposes typed getters for workflow config values.
70
+ - Applies defaults and environment variable indirection.
71
+ - Performs validation used by the orchestrator before dispatch.
72
+
73
+ 3. `Issue Tracker Client`
74
+ - Fetches candidate issues in active states.
75
+ - Fetches current states for specific issue IDs (reconciliation).
76
+ - Fetches terminal-state issues during startup cleanup.
77
+ - Normalizes tracker payloads into a stable issue model.
78
+
79
+ 4. `Orchestrator`
80
+ - Owns the poll tick.
81
+ - Owns the in-memory runtime state.
82
+ - Decides which issues to dispatch, retry, stop, or release.
83
+ - Tracks session metrics and retry queue state.
84
+
85
+ 5. `Workspace Manager`
86
+ - Maps issue identifiers to workspace paths.
87
+ - Ensures per-issue workspace directories exist.
88
+ - Runs workspace lifecycle hooks.
89
+ - Cleans workspaces for terminal issues.
90
+
91
+ 6. `Agent Runner`
92
+ - Creates workspace.
93
+ - Builds prompt from issue + workflow template.
94
+ - Launches the coding agent app-server client.
95
+ - Streams agent updates back to the orchestrator.
96
+
97
+ 7. `Status Surface` (optional)
98
+ - Presents human-readable runtime status (for example terminal output, dashboard, or other
99
+ operator-facing view).
100
+
101
+ 8. `Logging`
102
+ - Emits structured runtime logs to one or more configured sinks.
103
+
104
+ ### 3.2 Abstraction Levels
105
+
106
+ Symphony is easiest to port when kept in these layers:
107
+
108
+ 1. `Policy Layer` (repo-defined)
109
+ - `WORKFLOW.md` prompt body.
110
+ - Team-specific rules for ticket handling, validation, and handoff.
111
+
112
+ 2. `Configuration Layer` (typed getters)
113
+ - Parses front matter into typed runtime settings.
114
+ - Handles defaults, environment tokens, and path normalization.
115
+
116
+ 3. `Coordination Layer` (orchestrator)
117
+ - Polling loop, issue eligibility, concurrency, retries, reconciliation.
118
+
119
+ 4. `Execution Layer` (workspace + agent subprocess)
120
+ - Filesystem lifecycle, workspace preparation, coding-agent protocol.
121
+
122
+ 5. `Integration Layer` (Linear adapter)
123
+ - API calls and normalization for tracker data.
124
+
125
+ 6. `Observability Layer` (logs + optional status surface)
126
+ - Operator visibility into orchestrator and agent behavior.
127
+
128
+ ### 3.3 External Dependencies
129
+
130
+ - Issue tracker API (Linear for `tracker.kind: linear` in this specification version).
131
+ - Local filesystem for workspaces and logs.
132
+ - Optional workspace population tooling (for example Git CLI, if used).
133
+ - Coding-agent executable that supports JSON-RPC-like app-server mode over stdio.
134
+ - Host environment authentication for the issue tracker and coding agent.
135
+
136
+ ## 4. Core Domain Model
137
+
138
+ ### 4.1 Entities
139
+
140
+ #### 4.1.1 Issue
141
+
142
+ Normalized issue record used by orchestration, prompt rendering, and observability output.
143
+
144
+ Fields:
145
+
146
+ - `id` (string)
147
+ - Stable tracker-internal ID.
148
+ - `identifier` (string)
149
+ - Human-readable ticket key (example: `ABC-123`).
150
+ - `title` (string)
151
+ - `description` (string or null)
152
+ - `priority` (integer or null)
153
+ - Lower numbers are higher priority in dispatch sorting.
154
+ - `state` (string)
155
+ - Current tracker state name.
156
+ - `branch_name` (string or null)
157
+ - Tracker-provided branch metadata if available.
158
+ - `url` (string or null)
159
+ - `labels` (list of strings)
160
+ - Normalized to lowercase.
161
+ - `blocked_by` (list of blocker refs)
162
+ - Each blocker ref contains:
163
+ - `id` (string or null)
164
+ - `identifier` (string or null)
165
+ - `state` (string or null)
166
+ - `created_at` (timestamp or null)
167
+ - `updated_at` (timestamp or null)
168
+
169
+ #### 4.1.2 Workflow Definition
170
+
171
+ Parsed `WORKFLOW.md` payload:
172
+
173
+ - `config` (map)
174
+ - YAML front matter root object.
175
+ - `prompt_template` (string)
176
+ - Markdown body after front matter, trimmed.
177
+
178
+ #### 4.1.3 Service Config (Typed View)
179
+
180
+ Typed runtime values derived from `WorkflowDefinition.config` plus environment resolution.
181
+
182
+ Examples:
183
+
184
+ - poll interval
185
+ - workspace root
186
+ - active and terminal issue states
187
+ - concurrency limits
188
+ - coding-agent executable/args/timeouts
189
+ - workspace hooks
190
+
191
+ #### 4.1.4 Workspace
192
+
193
+ Filesystem workspace assigned to one issue identifier.
194
+
195
+ Fields (logical):
196
+
197
+ - `path` (workspace path; current runtime typically uses absolute paths, but relative roots are
198
+ possible if configured without path separators)
199
+ - `workspace_key` (sanitized issue identifier)
200
+ - `created_now` (boolean, used to gate `after_create` hook)
201
+
202
+ #### 4.1.5 Run Attempt
203
+
204
+ One execution attempt for one issue.
205
+
206
+ Fields (logical):
207
+
208
+ - `issue_id`
209
+ - `issue_identifier`
210
+ - `attempt` (integer or null, `null` for first run, `>=1` for retries/continuation)
211
+ - `workspace_path`
212
+ - `started_at`
213
+ - `status`
214
+ - `error` (optional)
215
+
216
+ #### 4.1.6 Live Session (Agent Session Metadata)
217
+
218
+ State tracked while a coding-agent subprocess is running.
219
+
220
+ Fields:
221
+
222
+ - `session_id` (string, `<thread_id>-<turn_id>`)
223
+ - `thread_id` (string)
224
+ - `turn_id` (string)
225
+ - `codex_app_server_pid` (string or null)
226
+ - `last_codex_event` (string/enum or null)
227
+ - `last_codex_timestamp` (timestamp or null)
228
+ - `last_codex_message` (summarized payload)
229
+ - `codex_input_tokens` (integer)
230
+ - `codex_output_tokens` (integer)
231
+ - `codex_total_tokens` (integer)
232
+ - `last_reported_input_tokens` (integer)
233
+ - `last_reported_output_tokens` (integer)
234
+ - `last_reported_total_tokens` (integer)
235
+ - `turn_count` (integer)
236
+ - Number of coding-agent turns started within the current worker lifetime.
237
+
238
+ #### 4.1.7 Retry Entry
239
+
240
+ Scheduled retry state for an issue.
241
+
242
+ Fields:
243
+
244
+ - `issue_id`
245
+ - `identifier` (best-effort human ID for status surfaces/logs)
246
+ - `attempt` (integer, 1-based for retry queue)
247
+ - `due_at_ms` (monotonic clock timestamp)
248
+ - `timer_handle` (runtime-specific timer reference)
249
+ - `error` (string or null)
250
+
251
+ #### 4.1.8 Orchestrator Runtime State
252
+
253
+ Single authoritative in-memory state owned by the orchestrator.
254
+
255
+ Fields:
256
+
257
+ - `poll_interval_ms` (current effective poll interval)
258
+ - `max_concurrent_agents` (current effective global concurrency limit)
259
+ - `running` (map `issue_id -> running entry`)
260
+ - `claimed` (set of issue IDs reserved/running/retrying)
261
+ - `retry_attempts` (map `issue_id -> RetryEntry`)
262
+ - `completed` (set of issue IDs; bookkeeping only, not dispatch gating)
263
+ - `codex_totals` (aggregate tokens + runtime seconds)
264
+ - `codex_rate_limits` (latest rate-limit snapshot from agent events)
265
+
266
+ ### 4.2 Stable Identifiers and Normalization Rules
267
+
268
+ - `Issue ID`
269
+ - Use for tracker lookups and internal map keys.
270
+ - `Issue Identifier`
271
+ - Use for human-readable logs and workspace naming.
272
+ - `Workspace Key`
273
+ - Derive from `issue.identifier` by replacing any character not in `[A-Za-z0-9._-]` with `_`.
274
+ - Use the sanitized value for the workspace directory name.
275
+ - `Normalized Issue State`
276
+ - Compare states after `lowercase`.
277
+ - `Session ID`
278
+ - Compose from coding-agent `thread_id` and `turn_id` as `<thread_id>-<turn_id>`.
279
+
280
+ ## 5. Workflow Specification (Repository Contract)
281
+
282
+ ### 5.1 File Discovery and Path Resolution
283
+
284
+ Workflow file path precedence:
285
+
286
+ 1. Explicit application/runtime setting (set by CLI startup path).
287
+ 2. Default: `WORKFLOW.md` in the current process working directory.
288
+
289
+ Loader behavior:
290
+
291
+ - If the file cannot be read, return `missing_workflow_file` error.
292
+ - The workflow file is expected to be repository-owned and version-controlled.
293
+
294
+ ### 5.2 File Format
295
+
296
+ `WORKFLOW.md` is a Markdown file with optional YAML front matter.
297
+
298
+ Design note:
299
+
300
+ - `WORKFLOW.md` should be self-contained enough to describe and run different workflows (prompt,
301
+ runtime settings, hooks, and tracker selection/config) without requiring out-of-band
302
+ service-specific configuration.
303
+
304
+ Parsing rules:
305
+
306
+ - If file starts with `---`, parse lines until the next `---` as YAML front matter.
307
+ - Remaining lines become the prompt body.
308
+ - If front matter is absent, treat the entire file as prompt body and use an empty config map.
309
+ - YAML front matter must decode to a map/object; non-map YAML is an error.
310
+ - Prompt body is trimmed before use.
311
+
312
+ Returned workflow object:
313
+
314
+ - `config`: front matter root object (not nested under a `config` key).
315
+ - `prompt_template`: trimmed Markdown body.
316
+
317
+ ### 5.3 Front Matter Schema
318
+
319
+ Top-level keys:
320
+
321
+ - `tracker`
322
+ - `polling`
323
+ - `workspace`
324
+ - `hooks`
325
+ - `agent`
326
+ - `codex`
327
+
328
+ Unknown keys should be ignored for forward compatibility.
329
+
330
+ Note:
331
+
332
+ - The workflow front matter is extensible. Optional extensions may define additional top-level keys
333
+ (for example `server`) without changing the core schema above.
334
+ - Extensions should document their field schema, defaults, validation rules, and whether changes
335
+ apply dynamically or require restart.
336
+ - Common extension: `server.port` (integer) enables the optional HTTP server described in Section
337
+ 13.7.
338
+
339
+ #### 5.3.1 `tracker` (object)
340
+
341
+ Fields:
342
+
343
+ - `kind` (string)
344
+ - Required for dispatch.
345
+ - Current supported value: `linear`
346
+ - `endpoint` (string)
347
+ - Default for `tracker.kind == "linear"`: `https://api.linear.app/graphql`
348
+ - `api_key` (string)
349
+ - May be a literal token or `$VAR_NAME`.
350
+ - Canonical environment variable for `tracker.kind == "linear"`: `LINEAR_API_KEY`.
351
+ - If `$VAR_NAME` resolves to an empty string, treat the key as missing.
352
+ - `project_slug` (string)
353
+ - Required for dispatch when `tracker.kind == "linear"`.
354
+ - `active_states` (list of strings)
355
+ - Default: `Todo`, `In Progress`
356
+ - `terminal_states` (list of strings)
357
+ - Default: `Closed`, `Cancelled`, `Canceled`, `Duplicate`, `Done`
358
+
359
+ #### 5.3.2 `polling` (object)
360
+
361
+ Fields:
362
+
363
+ - `interval_ms` (integer or string integer)
364
+ - Default: `30000`
365
+ - Changes should be re-applied at runtime and affect future tick scheduling without restart.
366
+
367
+ #### 5.3.3 `workspace` (object)
368
+
369
+ Fields:
370
+
371
+ - `root` (path string or `$VAR`)
372
+ - Default: `<system-temp>/symphony_workspaces`
373
+ - `~` and strings containing path separators are expanded.
374
+ - Bare strings without path separators are preserved as-is (relative roots are allowed but
375
+ discouraged).
376
+
377
+ #### 5.3.4 `hooks` (object)
378
+
379
+ Fields:
380
+
381
+ - `after_create` (multiline shell script string, optional)
382
+ - Runs only when a workspace directory is newly created.
383
+ - Failure aborts workspace creation.
384
+ - `before_run` (multiline shell script string, optional)
385
+ - Runs before each agent attempt after workspace preparation and before launching the coding
386
+ agent.
387
+ - Failure aborts the current attempt.
388
+ - `after_run` (multiline shell script string, optional)
389
+ - Runs after each agent attempt (success, failure, timeout, or cancellation) once the workspace
390
+ exists.
391
+ - Failure is logged but ignored.
392
+ - `before_remove` (multiline shell script string, optional)
393
+ - Runs before workspace deletion if the directory exists.
394
+ - Failure is logged but ignored; cleanup still proceeds.
395
+ - `timeout_ms` (integer, optional)
396
+ - Default: `60000`
397
+ - Applies to all workspace hooks.
398
+ - Non-positive values should be treated as invalid and fall back to the default.
399
+ - Changes should be re-applied at runtime for future hook executions.
400
+
401
+ #### 5.3.5 `agent` (object)
402
+
403
+ Fields:
404
+
405
+ - `max_concurrent_agents` (integer or string integer)
406
+ - Default: `10`
407
+ - Changes should be re-applied at runtime and affect subsequent dispatch decisions.
408
+ - `max_retry_backoff_ms` (integer or string integer)
409
+ - Default: `300000` (5 minutes)
410
+ - Changes should be re-applied at runtime and affect future retry scheduling.
411
+ - `max_concurrent_agents_by_state` (map `state_name -> positive integer`)
412
+ - Default: empty map.
413
+ - State keys are normalized (`lowercase`) for lookup.
414
+ - Invalid entries (non-positive or non-numeric) are ignored.
415
+
416
+ #### 5.3.6 `codex` (object)
417
+
418
+ Fields:
419
+
420
+ For Codex-owned config values such as `approval_policy`, `thread_sandbox`, and
421
+ `turn_sandbox_policy`, supported values are defined by the targeted Codex app-server version.
422
+ Implementors should treat them as pass-through Codex config values rather than relying on a
423
+ hand-maintained enum in this spec. To inspect the installed Codex schema, run
424
+ `codex app-server generate-json-schema --out <dir>` and inspect the relevant definitions referenced
425
+ by `v2/ThreadStartParams.json` and `v2/TurnStartParams.json`. Implementations may validate these
426
+ fields locally if they want stricter startup checks.
427
+
428
+ - `command` (string shell command)
429
+ - Default: `codex app-server`
430
+ - The runtime launches this command via `bash -lc` in the workspace directory.
431
+ - The launched process must speak a compatible app-server protocol over stdio.
432
+ - `approval_policy` (Codex `AskForApproval` value)
433
+ - Default: implementation-defined.
434
+ - `thread_sandbox` (Codex `SandboxMode` value)
435
+ - Default: implementation-defined.
436
+ - `turn_sandbox_policy` (Codex `SandboxPolicy` value)
437
+ - Default: implementation-defined.
438
+ - `turn_timeout_ms` (integer)
439
+ - Default: `3600000` (1 hour)
440
+ - `read_timeout_ms` (integer)
441
+ - Default: `5000`
442
+ - `stall_timeout_ms` (integer)
443
+ - Default: `300000` (5 minutes)
444
+ - If `<= 0`, stall detection is disabled.
445
+
446
+ ### 5.4 Prompt Template Contract
447
+
448
+ The Markdown body of `WORKFLOW.md` is the per-issue prompt template.
449
+
450
+ Rendering requirements:
451
+
452
+ - Use a strict template engine (Liquid-compatible semantics are sufficient).
453
+ - Unknown variables must fail rendering.
454
+ - Unknown filters must fail rendering.
455
+
456
+ Template input variables:
457
+
458
+ - `issue` (object)
459
+ - Includes all normalized issue fields, including labels and blockers.
460
+ - `attempt` (integer or null)
461
+ - `null`/absent on first attempt.
462
+ - Integer on retry or continuation run.
463
+
464
+ Fallback prompt behavior:
465
+
466
+ - If the workflow prompt body is empty, the runtime may use a minimal default prompt
467
+ (`You are working on an issue from Linear.`).
468
+ - Workflow file read/parse failures are configuration/validation errors and should not silently fall
469
+ back to a prompt.
470
+
471
+ ### 5.5 Workflow Validation and Error Surface
472
+
473
+ Error classes:
474
+
475
+ - `missing_workflow_file`
476
+ - `workflow_parse_error`
477
+ - `workflow_front_matter_not_a_map`
478
+ - `template_parse_error` (during prompt rendering)
479
+ - `template_render_error` (unknown variable/filter, invalid interpolation)
480
+
481
+ Dispatch gating behavior:
482
+
483
+ - Workflow file read/YAML errors block new dispatches until fixed.
484
+ - Template errors fail only the affected run attempt.
485
+
486
+ ## 6. Configuration Specification
487
+
488
+ ### 6.1 Source Precedence and Resolution Semantics
489
+
490
+ Configuration precedence:
491
+
492
+ 1. Workflow file path selection (runtime setting -> cwd default).
493
+ 2. YAML front matter values.
494
+ 3. Environment indirection via `$VAR_NAME` inside selected YAML values.
495
+ 4. Built-in defaults.
496
+
497
+ Value coercion semantics:
498
+
499
+ - Path/command fields support:
500
+ - `~` home expansion
501
+ - `$VAR` expansion for env-backed path values
502
+ - Apply expansion only to values intended to be local filesystem paths; do not rewrite URIs or
503
+ arbitrary shell command strings.
504
+
505
+ ### 6.2 Dynamic Reload Semantics
506
+
507
+ Dynamic reload is required:
508
+
509
+ - The software should watch `WORKFLOW.md` for changes.
510
+ - On change, it should re-read and re-apply workflow config and prompt template without restart.
511
+ - The software should attempt to adjust live behavior to the new config (for example polling
512
+ cadence, concurrency limits, active/terminal states, codex settings, workspace paths/hooks, and
513
+ prompt content for future runs).
514
+ - Reloaded config applies to future dispatch, retry scheduling, reconciliation decisions, hook
515
+ execution, and agent launches.
516
+ - Implementations are not required to restart in-flight agent sessions automatically when config
517
+ changes.
518
+ - Extensions that manage their own listeners/resources (for example an HTTP server port change) may
519
+ require restart unless the implementation explicitly supports live rebind.
520
+ - Implementations should also re-validate/reload defensively during runtime operations (for example
521
+ before dispatch) in case filesystem watch events are missed.
522
+ - Invalid reloads should not crash the service; keep operating with the last known good effective
523
+ configuration and emit an operator-visible error.
524
+
525
+ ### 6.3 Dispatch Preflight Validation
526
+
527
+ This validation is a scheduler preflight run before attempting to dispatch new work. It validates
528
+ the workflow/config needed to poll and launch workers, not a full audit of all possible workflow
529
+ behavior.
530
+
531
+ Startup validation:
532
+
533
+ - Validate configuration before starting the scheduling loop.
534
+ - If startup validation fails, fail startup and emit an operator-visible error.
535
+
536
+ Per-tick dispatch validation:
537
+
538
+ - Re-validate before each dispatch cycle.
539
+ - If validation fails, skip dispatch for that tick, keep reconciliation active, and emit an
540
+ operator-visible error.
541
+
542
+ Validation checks:
543
+
544
+ - Workflow file can be loaded and parsed.
545
+ - `tracker.kind` is present and supported.
546
+ - `tracker.api_key` is present after `$` resolution.
547
+ - `tracker.project_slug` is present when required by the selected tracker kind.
548
+ - `codex.command` is present and non-empty.
549
+
550
+ ### 6.4 Config Fields Summary (Cheat Sheet)
551
+
552
+ This section is intentionally redundant so a coding agent can implement the config layer quickly.
553
+
554
+ - `tracker.kind`: string, required, currently `linear`
555
+ - `tracker.endpoint`: string, default `https://api.linear.app/graphql` when `tracker.kind=linear`
556
+ - `tracker.api_key`: string or `$VAR`, canonical env `LINEAR_API_KEY` when `tracker.kind=linear`
557
+ - `tracker.project_slug`: string, required when `tracker.kind=linear`
558
+ - `tracker.active_states`: list of strings, default `["Todo", "In Progress"]`
559
+ - `tracker.terminal_states`: list of strings, default `["Closed", "Cancelled", "Canceled", "Duplicate", "Done"]`
560
+ - `polling.interval_ms`: integer, default `30000`
561
+ - `workspace.root`: path, default `<system-temp>/symphony_workspaces`
562
+ - `worker.ssh_hosts` (extension): list of SSH host strings, optional; when omitted, work runs
563
+ locally
564
+ - `worker.max_concurrent_agents_per_host` (extension): positive integer, optional; shared per-host
565
+ cap applied across configured SSH hosts
566
+ - `hooks.after_create`: shell script or null
567
+ - `hooks.before_run`: shell script or null
568
+ - `hooks.after_run`: shell script or null
569
+ - `hooks.before_remove`: shell script or null
570
+ - `hooks.timeout_ms`: integer, default `60000`
571
+ - `agent.max_concurrent_agents`: integer, default `10`
572
+ - `agent.max_turns`: integer, default `20`
573
+ - `agent.max_retry_backoff_ms`: integer, default `300000` (5m)
574
+ - `agent.max_concurrent_agents_by_state`: map of positive integers, default `{}`
575
+ - `codex.command`: shell command string, default `codex app-server`
576
+ - `codex.approval_policy`: Codex `AskForApproval` value, default implementation-defined
577
+ - `codex.thread_sandbox`: Codex `SandboxMode` value, default implementation-defined
578
+ - `codex.turn_sandbox_policy`: Codex `SandboxPolicy` value, default implementation-defined
579
+ - `codex.turn_timeout_ms`: integer, default `3600000`
580
+ - `codex.read_timeout_ms`: integer, default `5000`
581
+ - `codex.stall_timeout_ms`: integer, default `300000`
582
+ - `server.port` (extension): integer, optional; enables the optional HTTP server, `0` may be used
583
+ for ephemeral local bind, and CLI `--port` overrides it
584
+
585
+ ## 7. Orchestration State Machine
586
+
587
+ The orchestrator is the only component that mutates scheduling state. All worker outcomes are
588
+ reported back to it and converted into explicit state transitions.
589
+
590
+ ### 7.1 Issue Orchestration States
591
+
592
+ This is not the same as tracker states (`Todo`, `In Progress`, etc.). This is the service's internal
593
+ claim state.
594
+
595
+ 1. `Unclaimed`
596
+ - Issue is not running and has no retry scheduled.
597
+
598
+ 2. `Claimed`
599
+ - Orchestrator has reserved the issue to prevent duplicate dispatch.
600
+ - In practice, claimed issues are either `Running` or `RetryQueued`.
601
+
602
+ 3. `Running`
603
+ - Worker task exists and the issue is tracked in `running` map.
604
+
605
+ 4. `RetryQueued`
606
+ - Worker is not running, but a retry timer exists in `retry_attempts`.
607
+
608
+ 5. `Released`
609
+ - Claim removed because issue is terminal, non-active, missing, or retry path completed without
610
+ re-dispatch.
611
+
612
+ Important nuance:
613
+
614
+ - A successful worker exit does not mean the issue is done forever.
615
+ - The worker may continue through multiple back-to-back coding-agent turns before it exits.
616
+ - After each normal turn completion, the worker re-checks the tracker issue state.
617
+ - If the issue is still in an active state, the worker should start another turn on the same live
618
+ coding-agent thread in the same workspace, up to `agent.max_turns`.
619
+ - The first turn should use the full rendered task prompt.
620
+ - Continuation turns should send only continuation guidance to the existing thread, not resend the
621
+ original task prompt that is already present in thread history.
622
+ - Once the worker exits normally, the orchestrator still schedules a short continuation retry
623
+ (about 1 second) so it can re-check whether the issue remains active and needs another worker
624
+ session.
625
+
626
+ ### 7.2 Run Attempt Lifecycle
627
+
628
+ A run attempt transitions through these phases:
629
+
630
+ 1. `PreparingWorkspace`
631
+ 2. `BuildingPrompt`
632
+ 3. `LaunchingAgentProcess`
633
+ 4. `InitializingSession`
634
+ 5. `StreamingTurn`
635
+ 6. `Finishing`
636
+ 7. `Succeeded`
637
+ 8. `Failed`
638
+ 9. `TimedOut`
639
+ 10. `Stalled`
640
+ 11. `CanceledByReconciliation`
641
+
642
+ Distinct terminal reasons are important because retry logic and logs differ.
643
+
644
+ ### 7.3 Transition Triggers
645
+
646
+ - `Poll Tick`
647
+ - Reconcile active runs.
648
+ - Validate config.
649
+ - Fetch candidate issues.
650
+ - Dispatch until slots are exhausted.
651
+
652
+ - `Worker Exit (normal)`
653
+ - Remove running entry.
654
+ - Update aggregate runtime totals.
655
+ - Schedule continuation retry (attempt `1`) after the worker exhausts or finishes its in-process
656
+ turn loop.
657
+
658
+ - `Worker Exit (abnormal)`
659
+ - Remove running entry.
660
+ - Update aggregate runtime totals.
661
+ - Schedule exponential-backoff retry.
662
+
663
+ - `Codex Update Event`
664
+ - Update live session fields, token counters, and rate limits.
665
+
666
+ - `Retry Timer Fired`
667
+ - Re-fetch active candidates and attempt re-dispatch, or release claim if no longer eligible.
668
+
669
+ - `Reconciliation State Refresh`
670
+ - Stop runs whose issue states are terminal or no longer active.
671
+
672
+ - `Stall Timeout`
673
+ - Kill worker and schedule retry.
674
+
675
+ ### 7.4 Idempotency and Recovery Rules
676
+
677
+ - The orchestrator serializes state mutations through one authority to avoid duplicate dispatch.
678
+ - `claimed` and `running` checks are required before launching any worker.
679
+ - Reconciliation runs before dispatch on every tick.
680
+ - Restart recovery is tracker-driven and filesystem-driven (no durable orchestrator DB required).
681
+ - Startup terminal cleanup removes stale workspaces for issues already in terminal states.
682
+
683
+ ## 8. Polling, Scheduling, and Reconciliation
684
+
685
+ ### 8.1 Poll Loop
686
+
687
+ At startup, the service validates config, performs startup cleanup, schedules an immediate tick, and
688
+ then repeats every `polling.interval_ms`.
689
+
690
+ The effective poll interval should be updated when workflow config changes are re-applied.
691
+
692
+ Tick sequence:
693
+
694
+ 1. Reconcile running issues.
695
+ 2. Run dispatch preflight validation.
696
+ 3. Fetch candidate issues from tracker using active states.
697
+ 4. Sort issues by dispatch priority.
698
+ 5. Dispatch eligible issues while slots remain.
699
+ 6. Notify observability/status consumers of state changes.
700
+
701
+ If per-tick validation fails, dispatch is skipped for that tick, but reconciliation still happens
702
+ first.
703
+
704
+ ### 8.2 Candidate Selection Rules
705
+
706
+ An issue is dispatch-eligible only if all are true:
707
+
708
+ - It has `id`, `identifier`, `title`, and `state`.
709
+ - Its state is in `active_states` and not in `terminal_states`.
710
+ - It is not already in `running`.
711
+ - It is not already in `claimed`.
712
+ - Global concurrency slots are available.
713
+ - Per-state concurrency slots are available.
714
+ - Blocker rule for `Todo` state passes:
715
+ - If the issue state is `Todo`, do not dispatch when any blocker is non-terminal.
716
+
717
+ Sorting order (stable intent):
718
+
719
+ 1. `priority` ascending (1..4 are preferred; null/unknown sorts last)
720
+ 2. `created_at` oldest first
721
+ 3. `identifier` lexicographic tie-breaker
722
+
723
+ ### 8.3 Concurrency Control
724
+
725
+ Global limit:
726
+
727
+ - `available_slots = max(max_concurrent_agents - running_count, 0)`
728
+
729
+ Per-state limit:
730
+
731
+ - `max_concurrent_agents_by_state[state]` if present (state key normalized)
732
+ - otherwise fallback to global limit
733
+
734
+ The runtime counts issues by their current tracked state in the `running` map.
735
+
736
+ Optional SSH host limit:
737
+
738
+ - When `worker.max_concurrent_agents_per_host` is set, each configured SSH host may run at most
739
+ that many concurrent agents at once.
740
+ - Hosts at that cap are skipped for new dispatch until capacity frees up.
741
+
742
+ ### 8.4 Retry and Backoff
743
+
744
+ Retry entry creation:
745
+
746
+ - Cancel any existing retry timer for the same issue.
747
+ - Store `attempt`, `identifier`, `error`, `due_at_ms`, and new timer handle.
748
+
749
+ Backoff formula:
750
+
751
+ - Normal continuation retries after a clean worker exit use a short fixed delay of `1000` ms.
752
+ - Failure-driven retries use `delay = min(10000 * 2^(attempt - 1), agent.max_retry_backoff_ms)`.
753
+ - Power is capped by the configured max retry backoff (default `300000` / 5m).
754
+
755
+ Retry handling behavior:
756
+
757
+ 1. Fetch active candidate issues (not all issues).
758
+ 2. Find the specific issue by `issue_id`.
759
+ 3. If not found, release claim.
760
+ 4. If found and still candidate-eligible:
761
+ - Dispatch if slots are available.
762
+ - Otherwise requeue with error `no available orchestrator slots`.
763
+ 5. If found but no longer active, release claim.
764
+
765
+ Note:
766
+
767
+ - Terminal-state workspace cleanup is handled by startup cleanup and active-run reconciliation
768
+ (including terminal transitions for currently running issues).
769
+ - Retry handling mainly operates on active candidates and releases claims when the issue is absent,
770
+ rather than performing terminal cleanup itself.
771
+
772
+ ### 8.5 Active Run Reconciliation
773
+
774
+ Reconciliation runs every tick and has two parts.
775
+
776
+ Part A: Stall detection
777
+
778
+ - For each running issue, compute `elapsed_ms` since:
779
+ - `last_codex_timestamp` if any event has been seen, else
780
+ - `started_at`
781
+ - If `elapsed_ms > codex.stall_timeout_ms`, terminate the worker and queue a retry.
782
+ - If `stall_timeout_ms <= 0`, skip stall detection entirely.
783
+
784
+ Part B: Tracker state refresh
785
+
786
+ - Fetch current issue states for all running issue IDs.
787
+ - For each running issue:
788
+ - If tracker state is terminal: terminate worker and clean workspace.
789
+ - If tracker state is still active: update the in-memory issue snapshot.
790
+ - If tracker state is neither active nor terminal: terminate worker without workspace cleanup.
791
+ - If state refresh fails, keep workers running and try again on the next tick.
792
+
793
+ ### 8.6 Startup Terminal Workspace Cleanup
794
+
795
+ When the service starts:
796
+
797
+ 1. Query tracker for issues in terminal states.
798
+ 2. For each returned issue identifier, remove the corresponding workspace directory.
799
+ 3. If the terminal-issues fetch fails, log a warning and continue startup.
800
+
801
+ This prevents stale terminal workspaces from accumulating after restarts.
802
+
803
+ ## 9. Workspace Management and Safety
804
+
805
+ ### 9.1 Workspace Layout
806
+
807
+ Workspace root:
808
+
809
+ - `workspace.root` (normalized path; the current config layer expands path-like values and preserves
810
+ bare relative names)
811
+
812
+ Per-issue workspace path:
813
+
814
+ - `<workspace.root>/<sanitized_issue_identifier>`
815
+
816
+ Workspace persistence:
817
+
818
+ - Workspaces are reused across runs for the same issue.
819
+ - Successful runs do not auto-delete workspaces.
820
+
821
+ ### 9.2 Workspace Creation and Reuse
822
+
823
+ Input: `issue.identifier`
824
+
825
+ Algorithm summary:
826
+
827
+ 1. Sanitize identifier to `workspace_key`.
828
+ 2. Compute workspace path under workspace root.
829
+ 3. Ensure the workspace path exists as a directory.
830
+ 4. Mark `created_now=true` only if the directory was created during this call; otherwise
831
+ `created_now=false`.
832
+ 5. If `created_now=true`, run `after_create` hook if configured.
833
+
834
+ Notes:
835
+
836
+ - This section does not assume any specific repository/VCS workflow.
837
+ - Workspace preparation beyond directory creation (for example dependency bootstrap, checkout/sync,
838
+ code generation) is implementation-defined and is typically handled via hooks.
839
+
840
+ ### 9.3 Optional Workspace Population (Implementation-Defined)
841
+
842
+ The spec does not require any built-in VCS or repository bootstrap behavior.
843
+
844
+ Implementations may populate or synchronize the workspace using implementation-defined logic and/or
845
+ hooks (for example `after_create` and/or `before_run`).
846
+
847
+ Failure handling:
848
+
849
+ - Workspace population/synchronization failures return an error for the current attempt.
850
+ - If failure happens while creating a brand-new workspace, implementations may remove the partially
851
+ prepared directory.
852
+ - Reused workspaces should not be destructively reset on population failure unless that policy is
853
+ explicitly chosen and documented.
854
+
855
+ ### 9.4 Workspace Hooks
856
+
857
+ Supported hooks:
858
+
859
+ - `hooks.after_create`
860
+ - `hooks.before_run`
861
+ - `hooks.after_run`
862
+ - `hooks.before_remove`
863
+
864
+ Execution contract:
865
+
866
+ - Execute in a local shell context appropriate to the host OS, with the workspace directory as
867
+ `cwd`.
868
+ - On POSIX systems, `sh -lc <script>` (or a stricter equivalent such as `bash -lc <script>`) is a
869
+ conforming default.
870
+ - Hook timeout uses `hooks.timeout_ms`; default: `60000 ms`.
871
+ - Log hook start, failures, and timeouts.
872
+
873
+ Failure semantics:
874
+
875
+ - `after_create` failure or timeout is fatal to workspace creation.
876
+ - `before_run` failure or timeout is fatal to the current run attempt.
877
+ - `after_run` failure or timeout is logged and ignored.
878
+ - `before_remove` failure or timeout is logged and ignored.
879
+
880
+ ### 9.5 Safety Invariants
881
+
882
+ This is the most important portability constraint.
883
+
884
+ Invariant 1: Run the coding agent only in the per-issue workspace path.
885
+
886
+ - Before launching the coding-agent subprocess, validate:
887
+ - `cwd == workspace_path`
888
+
889
+ Invariant 2: Workspace path must stay inside workspace root.
890
+
891
+ - Normalize both paths to absolute.
892
+ - Require `workspace_path` to have `workspace_root` as a prefix directory.
893
+ - Reject any path outside the workspace root.
894
+
895
+ Invariant 3: Workspace key is sanitized.
896
+
897
+ - Only `[A-Za-z0-9._-]` allowed in workspace directory names.
898
+ - Replace all other characters with `_`.
899
+
900
+ ## 10. Agent Runner Protocol (Coding Agent Integration)
901
+
902
+ This section defines the language-neutral contract for integrating a coding agent app-server.
903
+
904
+ Compatibility profile:
905
+
906
+ - The normative contract is message ordering, required behaviors, and the logical fields that must
907
+ be extracted (for example session IDs, completion state, approval handling, and usage/rate-limit
908
+ telemetry).
909
+ - Exact JSON field names may vary slightly across compatible app-server versions.
910
+ - Implementations should tolerate equivalent payload shapes when they carry the same logical
911
+ meaning, especially for nested IDs, approval requests, user-input-required signals, and
912
+ token/rate-limit metadata.
913
+
914
+ ### 10.1 Launch Contract
915
+
916
+ Subprocess launch parameters:
917
+
918
+ - Command: `codex.command`
919
+ - Invocation: `bash -lc <codex.command>`
920
+ - Working directory: workspace path
921
+ - Stdout/stderr: separate streams
922
+ - Framing: line-delimited protocol messages on stdout (JSON-RPC-like JSON per line)
923
+
924
+ Notes:
925
+
926
+ - The default command is `codex app-server`.
927
+ - Approval policy, cwd, and prompt are expressed in the protocol messages in Section 10.2.
928
+
929
+ Recommended additional process settings:
930
+
931
+ - Max line size: 10 MB (for safe buffering)
932
+
933
+ ### 10.2 Session Startup Handshake
934
+
935
+ Reference: https://developers.openai.com/codex/app-server/
936
+
937
+ The client must send these protocol messages in order:
938
+
939
+ Illustrative startup transcript (equivalent payload shapes are acceptable if they preserve the same
940
+ semantics):
941
+
942
+ ```json
943
+ {"id":1,"method":"initialize","params":{"clientInfo":{"name":"symphony","version":"1.0"},"capabilities":{}}}
944
+ {"method":"initialized","params":{}}
945
+ {"id":2,"method":"thread/start","params":{"approvalPolicy":"<implementation-defined>","sandbox":"<implementation-defined>","cwd":"/abs/workspace"}}
946
+ {"id":3,"method":"turn/start","params":{"threadId":"<thread-id>","input":[{"type":"text","text":"<rendered prompt-or-continuation-guidance>"}],"cwd":"/abs/workspace","title":"ABC-123: Example","approvalPolicy":"<implementation-defined>","sandboxPolicy":{"type":"<implementation-defined>"}}}
947
+ ```
948
+
949
+ 1. `initialize` request
950
+ - Params include:
951
+ - `clientInfo` object (for example `{name, version}`)
952
+ - `capabilities` object (may be empty)
953
+ - If the targeted Codex app-server requires capability negotiation for dynamic tools, include the
954
+ necessary capability flag(s) here.
955
+ - Wait for response (`read_timeout_ms`)
956
+ 2. `initialized` notification
957
+ 3. `thread/start` request
958
+ - Params include:
959
+ - `approvalPolicy` = implementation-defined session approval policy value
960
+ - `sandbox` = implementation-defined session sandbox value
961
+ - `cwd` = absolute workspace path
962
+ - If optional client-side tools are implemented, include their advertised tool specs using the
963
+ protocol mechanism supported by the targeted Codex app-server version.
964
+ 4. `turn/start` request
965
+ - Params include:
966
+ - `threadId`
967
+ - `input` = single text item containing rendered prompt for the first turn, or continuation
968
+ guidance for later turns on the same thread
969
+ - `cwd`
970
+ - `title` = `<issue.identifier>: <issue.title>`
971
+ - `approvalPolicy` = implementation-defined turn approval policy value
972
+ - `sandboxPolicy` = implementation-defined object-form sandbox policy payload when required by
973
+ the targeted app-server version
974
+
975
+ Session identifiers:
976
+
977
+ - Read `thread_id` from `thread/start` result `result.thread.id`
978
+ - Read `turn_id` from each `turn/start` result `result.turn.id`
979
+ - Emit `session_id = "<thread_id>-<turn_id>"`
980
+ - Reuse the same `thread_id` for all continuation turns inside one worker run
981
+
982
+ ### 10.3 Streaming Turn Processing
983
+
984
+ The client reads line-delimited messages until the turn terminates.
985
+
986
+ Completion conditions:
987
+
988
+ - `turn/completed` -> success
989
+ - `turn/failed` -> failure
990
+ - `turn/cancelled` -> failure
991
+ - turn timeout (`turn_timeout_ms`) -> failure
992
+ - subprocess exit -> failure
993
+
994
+ Continuation processing:
995
+
996
+ - If the worker decides to continue after a successful turn, it should issue another `turn/start`
997
+ on the same live `threadId`.
998
+ - The app-server subprocess should remain alive across those continuation turns and be stopped only
999
+ when the worker run is ending.
1000
+
1001
+ Line handling requirements:
1002
+
1003
+ - Read protocol messages from stdout only.
1004
+ - Buffer partial stdout lines until newline arrives.
1005
+ - Attempt JSON parse on complete stdout lines.
1006
+ - Stderr is not part of the protocol stream:
1007
+ - ignore it or log it as diagnostics
1008
+ - do not attempt protocol JSON parsing on stderr
1009
+
1010
+ ### 10.4 Emitted Runtime Events (Upstream to Orchestrator)
1011
+
1012
+ The app-server client emits structured events to the orchestrator callback. Each event should
1013
+ include:
1014
+
1015
+ - `event` (enum/string)
1016
+ - `timestamp` (UTC timestamp)
1017
+ - `codex_app_server_pid` (if available)
1018
+ - optional `usage` map (token counts)
1019
+ - payload fields as needed
1020
+
1021
+ Important emitted events may include:
1022
+
1023
+ - `session_started`
1024
+ - `startup_failed`
1025
+ - `turn_completed`
1026
+ - `turn_failed`
1027
+ - `turn_cancelled`
1028
+ - `turn_ended_with_error`
1029
+ - `turn_input_required`
1030
+ - `approval_auto_approved`
1031
+ - `unsupported_tool_call`
1032
+ - `notification`
1033
+ - `other_message`
1034
+ - `malformed`
1035
+
1036
+ ### 10.5 Approval, Tool Calls, and User Input Policy
1037
+
1038
+ Approval, sandbox, and user-input behavior is implementation-defined.
1039
+
1040
+ Policy requirements:
1041
+
1042
+ - Each implementation should document its chosen approval, sandbox, and operator-confirmation
1043
+ posture.
1044
+ - Approval requests and user-input-required events must not leave a run stalled indefinitely. An
1045
+ implementation should either satisfy them, surface them to an operator, auto-resolve them, or
1046
+ fail the run according to its documented policy.
1047
+
1048
+ Example high-trust behavior:
1049
+
1050
+ - Auto-approve command execution approvals for the session.
1051
+ - Auto-approve file-change approvals for the session.
1052
+ - Treat user-input-required turns as hard failure.
1053
+
1054
+ Unsupported dynamic tool calls:
1055
+
1056
+ - Supported dynamic tool calls that are explicitly implemented and advertised by the runtime should
1057
+ be handled according to their extension contract.
1058
+ - If the agent requests a dynamic tool call (`item/tool/call`) that is not supported, return a tool
1059
+ failure response and continue the session.
1060
+ - This prevents the session from stalling on unsupported tool execution paths.
1061
+
1062
+ Optional client-side tool extension:
1063
+
1064
+ - An implementation may expose a limited set of client-side tools to the app-server session.
1065
+ - Current optional standardized tool: `linear_graphql`.
1066
+ - If implemented, supported tools should be advertised to the app-server session during startup
1067
+ using the protocol mechanism supported by the targeted Codex app-server version.
1068
+ - Unsupported tool names should still return a failure result and continue the session.
1069
+
1070
+ `linear_graphql` extension contract:
1071
+
1072
+ - Purpose: execute a raw GraphQL query or mutation against Linear using Symphony's configured
1073
+ tracker auth for the current session.
1074
+ - Availability: only meaningful when `tracker.kind == "linear"` and valid Linear auth is configured.
1075
+ - Preferred input shape:
1076
+
1077
+ ```json
1078
+ {
1079
+ "query": "single GraphQL query or mutation document",
1080
+ "variables": {
1081
+ "optional": "graphql variables object"
1082
+ }
1083
+ }
1084
+ ```
1085
+
1086
+ - `query` must be a non-empty string.
1087
+ - `query` must contain exactly one GraphQL operation.
1088
+ - `variables` is optional and, when present, must be a JSON object.
1089
+ - Implementations may additionally accept a raw GraphQL query string as shorthand input.
1090
+ - Execute one GraphQL operation per tool call.
1091
+ - If the provided document contains multiple operations, reject the tool call as invalid input.
1092
+ - `operationName` selection is intentionally out of scope for this extension.
1093
+ - Reuse the configured Linear endpoint and auth from the active Symphony workflow/runtime config; do
1094
+ not require the coding agent to read raw tokens from disk.
1095
+ - Tool result semantics:
1096
+ - transport success + no top-level GraphQL `errors` -> `success=true`
1097
+ - top-level GraphQL `errors` present -> `success=false`, but preserve the GraphQL response body
1098
+ for debugging
1099
+ - invalid input, missing auth, or transport failure -> `success=false` with an error payload
1100
+ - Return the GraphQL response or error payload as structured tool output that the model can inspect
1101
+ in-session.
1102
+
1103
+ Illustrative responses (equivalent payload shapes are acceptable if they preserve the same outcome):
1104
+
1105
+ ```json
1106
+ {"id":"<approval-id>","result":{"approved":true}}
1107
+ {"id":"<tool-call-id>","result":{"success":false,"error":"unsupported_tool_call"}}
1108
+ ```
1109
+
1110
+ Hard failure on user input requirement:
1111
+
1112
+ - If the agent requests user input, fail the run attempt immediately.
1113
+ - The client detects this via:
1114
+ - explicit method (`item/tool/requestUserInput`), or
1115
+ - turn methods/flags indicating input is required.
1116
+
1117
+ ### 10.6 Timeouts and Error Mapping
1118
+
1119
+ Timeouts:
1120
+
1121
+ - `codex.read_timeout_ms`: request/response timeout during startup and sync requests
1122
+ - `codex.turn_timeout_ms`: total turn stream timeout
1123
+ - `codex.stall_timeout_ms`: enforced by orchestrator based on event inactivity
1124
+
1125
+ Error mapping (recommended normalized categories):
1126
+
1127
+ - `codex_not_found`
1128
+ - `invalid_workspace_cwd`
1129
+ - `response_timeout`
1130
+ - `turn_timeout`
1131
+ - `port_exit`
1132
+ - `response_error`
1133
+ - `turn_failed`
1134
+ - `turn_cancelled`
1135
+ - `turn_input_required`
1136
+
1137
+ ### 10.7 Agent Runner Contract
1138
+
1139
+ The `Agent Runner` wraps workspace + prompt + app-server client.
1140
+
1141
+ Behavior:
1142
+
1143
+ 1. Create/reuse workspace for issue.
1144
+ 2. Build prompt from workflow template.
1145
+ 3. Start app-server session.
1146
+ 4. Forward app-server events to orchestrator.
1147
+ 5. On any error, fail the worker attempt (the orchestrator will retry).
1148
+
1149
+ Note:
1150
+
1151
+ - Workspaces are intentionally preserved after successful runs.
1152
+
1153
+ ## 11. Issue Tracker Integration Contract (Linear-Compatible)
1154
+
1155
+ ### 11.1 Required Operations
1156
+
1157
+ An implementation must support these tracker adapter operations:
1158
+
1159
+ 1. `fetch_candidate_issues()`
1160
+ - Return issues in configured active states for a configured project.
1161
+
1162
+ 2. `fetch_issues_by_states(state_names)`
1163
+ - Used for startup terminal cleanup.
1164
+
1165
+ 3. `fetch_issue_states_by_ids(issue_ids)`
1166
+ - Used for active-run reconciliation.
1167
+
1168
+ ### 11.2 Query Semantics (Linear)
1169
+
1170
+ Linear-specific requirements for `tracker.kind == "linear"`:
1171
+
1172
+ - `tracker.kind == "linear"`
1173
+ - GraphQL endpoint (default `https://api.linear.app/graphql`)
1174
+ - Auth token sent in `Authorization` header
1175
+ - `tracker.project_slug` maps to Linear project `slugId`
1176
+ - Candidate issue query filters project using `project: { slugId: { eq: $projectSlug } }`
1177
+ - Issue-state refresh query uses GraphQL issue IDs with variable type `[ID!]`
1178
+ - Pagination required for candidate issues
1179
+ - Page size default: `50`
1180
+ - Network timeout: `30000 ms`
1181
+
1182
+ Important:
1183
+
1184
+ - Linear GraphQL schema details can drift. Keep query construction isolated and test the exact query
1185
+ fields/types required by this specification.
1186
+
1187
+ A non-Linear implementation may change transport details, but the normalized outputs must match the
1188
+ domain model in Section 4.
1189
+
1190
+ ### 11.3 Normalization Rules
1191
+
1192
+ Candidate issue normalization should produce fields listed in Section 4.1.1.
1193
+
1194
+ Additional normalization details:
1195
+
1196
+ - `labels` -> lowercase strings
1197
+ - `blocked_by` -> derived from inverse relations where relation type is `blocks`
1198
+ - `priority` -> integer only (non-integers become null)
1199
+ - `created_at` and `updated_at` -> parse ISO-8601 timestamps
1200
+
1201
+ ### 11.4 Error Handling Contract
1202
+
1203
+ Recommended error categories:
1204
+
1205
+ - `unsupported_tracker_kind`
1206
+ - `missing_tracker_api_key`
1207
+ - `missing_tracker_project_slug`
1208
+ - `linear_api_request` (transport failures)
1209
+ - `linear_api_status` (non-200 HTTP)
1210
+ - `linear_graphql_errors`
1211
+ - `linear_unknown_payload`
1212
+ - `linear_missing_end_cursor` (pagination integrity error)
1213
+
1214
+ Orchestrator behavior on tracker errors:
1215
+
1216
+ - Candidate fetch failure: log and skip dispatch for this tick.
1217
+ - Running-state refresh failure: log and keep active workers running.
1218
+ - Startup terminal cleanup failure: log warning and continue startup.
1219
+
1220
+ ### 11.5 Tracker Writes (Important Boundary)
1221
+
1222
+ Symphony does not require first-class tracker write APIs in the orchestrator.
1223
+
1224
+ - Ticket mutations (state transitions, comments, PR metadata) are typically handled by the coding
1225
+ agent using tools defined by the workflow prompt.
1226
+ - The service remains a scheduler/runner and tracker reader.
1227
+ - Workflow-specific success often means "reached the next handoff state" (for example
1228
+ `Human Review`) rather than tracker terminal state `Done`.
1229
+ - If the optional `linear_graphql` client-side tool extension is implemented, it is still part of
1230
+ the agent toolchain rather than orchestrator business logic.
1231
+
1232
+ ## 12. Prompt Construction and Context Assembly
1233
+
1234
+ ### 12.1 Inputs
1235
+
1236
+ Inputs to prompt rendering:
1237
+
1238
+ - `workflow.prompt_template`
1239
+ - normalized `issue` object
1240
+ - optional `attempt` integer (retry/continuation metadata)
1241
+
1242
+ ### 12.2 Rendering Rules
1243
+
1244
+ - Render with strict variable checking.
1245
+ - Render with strict filter checking.
1246
+ - Convert issue object keys to strings for template compatibility.
1247
+ - Preserve nested arrays/maps (labels, blockers) so templates can iterate.
1248
+
1249
+ ### 12.3 Retry/Continuation Semantics
1250
+
1251
+ `attempt` should be passed to the template because the workflow prompt may provide different
1252
+ instructions for:
1253
+
1254
+ - first run (`attempt` null or absent)
1255
+ - continuation run after a successful prior session
1256
+ - retry after error/timeout/stall
1257
+
1258
+ ### 12.4 Failure Semantics
1259
+
1260
+ If prompt rendering fails:
1261
+
1262
+ - Fail the run attempt immediately.
1263
+ - Let the orchestrator treat it like any other worker failure and decide retry behavior.
1264
+
1265
+ ## 13. Logging, Status, and Observability
1266
+
1267
+ ### 13.1 Logging Conventions
1268
+
1269
+ Required context fields for issue-related logs:
1270
+
1271
+ - `issue_id`
1272
+ - `issue_identifier`
1273
+
1274
+ Required context for coding-agent session lifecycle logs:
1275
+
1276
+ - `session_id`
1277
+
1278
+ Message formatting requirements:
1279
+
1280
+ - Use stable `key=value` phrasing.
1281
+ - Include action outcome (`completed`, `failed`, `retrying`, etc.).
1282
+ - Include concise failure reason when present.
1283
+ - Avoid logging large raw payloads unless necessary.
1284
+
1285
+ ### 13.2 Logging Outputs and Sinks
1286
+
1287
+ The spec does not prescribe where logs must go (stderr, file, remote sink, etc.).
1288
+
1289
+ Requirements:
1290
+
1291
+ - Operators must be able to see startup/validation/dispatch failures without attaching a debugger.
1292
+ - Implementations may write to one or more sinks.
1293
+ - If a configured log sink fails, the service should continue running when possible and emit an
1294
+ operator-visible warning through any remaining sink.
1295
+
1296
+ ### 13.3 Runtime Snapshot / Monitoring Interface (Optional but Recommended)
1297
+
1298
+ If the implementation exposes a synchronous runtime snapshot (for dashboards or monitoring), it
1299
+ should return:
1300
+
1301
+ - `running` (list of running session rows)
1302
+ - each running row should include `turn_count`
1303
+ - `retrying` (list of retry queue rows)
1304
+ - `codex_totals`
1305
+ - `input_tokens`
1306
+ - `output_tokens`
1307
+ - `total_tokens`
1308
+ - `seconds_running` (aggregate runtime seconds as of snapshot time, including active sessions)
1309
+ - `rate_limits` (latest coding-agent rate limit payload, if available)
1310
+
1311
+ Recommended snapshot error modes:
1312
+
1313
+ - `timeout`
1314
+ - `unavailable`
1315
+
1316
+ ### 13.4 Optional Human-Readable Status Surface
1317
+
1318
+ A human-readable status surface (terminal output, dashboard, etc.) is optional and
1319
+ implementation-defined.
1320
+
1321
+ If present, it should draw from orchestrator state/metrics only and must not be required for
1322
+ correctness.
1323
+
1324
+ ### 13.5 Session Metrics and Token Accounting
1325
+
1326
+ Token accounting rules:
1327
+
1328
+ - Agent events may include token counts in multiple payload shapes.
1329
+ - Prefer absolute thread totals when available, such as:
1330
+ - `thread/tokenUsage/updated` payloads
1331
+ - `total_token_usage` within token-count wrapper events
1332
+ - Ignore delta-style payloads such as `last_token_usage` for dashboard/API totals.
1333
+ - Extract input/output/total token counts leniently from common field names within the selected
1334
+ payload.
1335
+ - For absolute totals, track deltas relative to last reported totals to avoid double-counting.
1336
+ - Do not treat generic `usage` maps as cumulative totals unless the event type defines them that
1337
+ way.
1338
+ - Accumulate aggregate totals in orchestrator state.
1339
+
1340
+ Runtime accounting:
1341
+
1342
+ - Runtime should be reported as a live aggregate at snapshot/render time.
1343
+ - Implementations may maintain a cumulative counter for ended sessions and add active-session
1344
+ elapsed time derived from `running` entries (for example `started_at`) when producing a
1345
+ snapshot/status view.
1346
+ - Add run duration seconds to the cumulative ended-session runtime when a session ends (normal exit
1347
+ or cancellation/termination).
1348
+ - Continuous background ticking of runtime totals is not required.
1349
+
1350
+ Rate-limit tracking:
1351
+
1352
+ - Track the latest rate-limit payload seen in any agent update.
1353
+ - Any human-readable presentation of rate-limit data is implementation-defined.
1354
+
1355
+ ### 13.6 Humanized Agent Event Summaries (Optional)
1356
+
1357
+ Humanized summaries of raw agent protocol events are optional.
1358
+
1359
+ If implemented:
1360
+
1361
+ - Treat them as observability-only output.
1362
+ - Do not make orchestrator logic depend on humanized strings.
1363
+
1364
+ ### 13.7 Optional HTTP Server Extension
1365
+
1366
+ This section defines an optional HTTP interface for observability and operational control.
1367
+
1368
+ If implemented:
1369
+
1370
+ - The HTTP server is an extension and is not required for conformance.
1371
+ - The implementation may serve server-rendered HTML or a client-side application for the dashboard.
1372
+ - The dashboard/API must be observability/control surfaces only and must not become required for
1373
+ orchestrator correctness.
1374
+
1375
+ Enablement (extension):
1376
+
1377
+ - Start the HTTP server when a CLI `--port` argument is provided.
1378
+ - Start the HTTP server when `server.port` is present in `WORKFLOW.md` front matter.
1379
+ - `server.port` is extension configuration and is intentionally not part of the core front-matter
1380
+ schema in Section 5.3.
1381
+ - Precedence: CLI `--port` overrides `server.port` when both are present.
1382
+ - `server.port` must be an integer. Positive values bind that port. `0` may be used to request an
1383
+ ephemeral port for local development and tests.
1384
+ - Implementations should bind loopback by default (`127.0.0.1` or host equivalent) unless explicitly
1385
+ configured otherwise.
1386
+ - Changes to HTTP listener settings (for example `server.port`) do not need to hot-rebind;
1387
+ restart-required behavior is conformant.
1388
+
1389
+ #### 13.7.1 Human-Readable Dashboard (`/`)
1390
+
1391
+ - Host a human-readable dashboard at `/`.
1392
+ - The returned document should depict the current state of the system (for example active sessions,
1393
+ retry delays, token consumption, runtime totals, recent events, and health/error indicators).
1394
+ - It is up to the implementation whether this is server-generated HTML or a client-side app that
1395
+ consumes the JSON API below.
1396
+
1397
+ #### 13.7.2 JSON REST API (`/api/v1/*`)
1398
+
1399
+ Provide a JSON REST API under `/api/v1/*` for current runtime state and operational debugging.
1400
+
1401
+ Minimum endpoints:
1402
+
1403
+ - `GET /api/v1/state`
1404
+ - Returns a summary view of the current system state (running sessions, retry queue/delays,
1405
+ aggregate token/runtime totals, latest rate limits, and any additional tracked summary fields).
1406
+ - Suggested response shape:
1407
+
1408
+ ```json
1409
+ {
1410
+ "generated_at": "2026-02-24T20:15:30Z",
1411
+ "counts": {
1412
+ "running": 2,
1413
+ "retrying": 1
1414
+ },
1415
+ "running": [
1416
+ {
1417
+ "issue_id": "abc123",
1418
+ "issue_identifier": "MT-649",
1419
+ "state": "In Progress",
1420
+ "session_id": "thread-1-turn-1",
1421
+ "turn_count": 7,
1422
+ "last_event": "turn_completed",
1423
+ "last_message": "",
1424
+ "started_at": "2026-02-24T20:10:12Z",
1425
+ "last_event_at": "2026-02-24T20:14:59Z",
1426
+ "tokens": {
1427
+ "input_tokens": 1200,
1428
+ "output_tokens": 800,
1429
+ "total_tokens": 2000
1430
+ }
1431
+ }
1432
+ ],
1433
+ "retrying": [
1434
+ {
1435
+ "issue_id": "def456",
1436
+ "issue_identifier": "MT-650",
1437
+ "attempt": 3,
1438
+ "due_at": "2026-02-24T20:16:00Z",
1439
+ "error": "no available orchestrator slots"
1440
+ }
1441
+ ],
1442
+ "codex_totals": {
1443
+ "input_tokens": 5000,
1444
+ "output_tokens": 2400,
1445
+ "total_tokens": 7400,
1446
+ "seconds_running": 1834.2
1447
+ },
1448
+ "rate_limits": null
1449
+ }
1450
+ ```
1451
+
1452
+ - `GET /api/v1/<issue_identifier>`
1453
+ - Returns issue-specific runtime/debug details for the identified issue, including any information
1454
+ the implementation tracks that is useful for debugging.
1455
+ - Suggested response shape:
1456
+
1457
+ ```json
1458
+ {
1459
+ "issue_identifier": "MT-649",
1460
+ "issue_id": "abc123",
1461
+ "status": "running",
1462
+ "workspace": {
1463
+ "path": "/tmp/symphony_workspaces/MT-649"
1464
+ },
1465
+ "attempts": {
1466
+ "restart_count": 1,
1467
+ "current_retry_attempt": 2
1468
+ },
1469
+ "running": {
1470
+ "session_id": "thread-1-turn-1",
1471
+ "turn_count": 7,
1472
+ "state": "In Progress",
1473
+ "started_at": "2026-02-24T20:10:12Z",
1474
+ "last_event": "notification",
1475
+ "last_message": "Working on tests",
1476
+ "last_event_at": "2026-02-24T20:14:59Z",
1477
+ "tokens": {
1478
+ "input_tokens": 1200,
1479
+ "output_tokens": 800,
1480
+ "total_tokens": 2000
1481
+ }
1482
+ },
1483
+ "retry": null,
1484
+ "logs": {
1485
+ "codex_session_logs": [
1486
+ {
1487
+ "label": "latest",
1488
+ "path": "/var/log/symphony/codex/MT-649/latest.log",
1489
+ "url": null
1490
+ }
1491
+ ]
1492
+ },
1493
+ "recent_events": [
1494
+ {
1495
+ "at": "2026-02-24T20:14:59Z",
1496
+ "event": "notification",
1497
+ "message": "Working on tests"
1498
+ }
1499
+ ],
1500
+ "last_error": null,
1501
+ "tracked": {}
1502
+ }
1503
+ ```
1504
+
1505
+ - If the issue is unknown to the current in-memory state, return `404` with an error response (for
1506
+ example `{\"error\":{\"code\":\"issue_not_found\",\"message\":\"...\"}}`).
1507
+
1508
+ - `POST /api/v1/refresh`
1509
+ - Queues an immediate tracker poll + reconciliation cycle (best-effort trigger; implementations
1510
+ may coalesce repeated requests).
1511
+ - Suggested request body: empty body or `{}`.
1512
+ - Suggested response (`202 Accepted`) shape:
1513
+
1514
+ ```json
1515
+ {
1516
+ "queued": true,
1517
+ "coalesced": false,
1518
+ "requested_at": "2026-02-24T20:15:30Z",
1519
+ "operations": ["poll", "reconcile"]
1520
+ }
1521
+ ```
1522
+
1523
+ API design notes:
1524
+
1525
+ - The JSON shapes above are the recommended baseline for interoperability and debugging ergonomics.
1526
+ - Implementations may add fields, but should avoid breaking existing fields within a version.
1527
+ - Endpoints should be read-only except for operational triggers like `/refresh`.
1528
+ - Unsupported methods on defined routes should return `405 Method Not Allowed`.
1529
+ - API errors should use a JSON envelope such as `{"error":{"code":"...","message":"..."}}`.
1530
+ - If the dashboard is a client-side app, it should consume this API rather than duplicating state
1531
+ logic.
1532
+
1533
+ ## 14. Failure Model and Recovery Strategy
1534
+
1535
+ ### 14.1 Failure Classes
1536
+
1537
+ 1. `Workflow/Config Failures`
1538
+ - Missing `WORKFLOW.md`
1539
+ - Invalid YAML front matter
1540
+ - Unsupported tracker kind or missing tracker credentials/project slug
1541
+ - Missing coding-agent executable
1542
+
1543
+ 2. `Workspace Failures`
1544
+ - Workspace directory creation failure
1545
+ - Workspace population/synchronization failure (implementation-defined; may come from hooks)
1546
+ - Invalid workspace path configuration
1547
+ - Hook timeout/failure
1548
+
1549
+ 3. `Agent Session Failures`
1550
+ - Startup handshake failure
1551
+ - Turn failed/cancelled
1552
+ - Turn timeout
1553
+ - User input requested (hard fail)
1554
+ - Subprocess exit
1555
+ - Stalled session (no activity)
1556
+
1557
+ 4. `Tracker Failures`
1558
+ - API transport errors
1559
+ - Non-200 status
1560
+ - GraphQL errors
1561
+ - malformed payloads
1562
+
1563
+ 5. `Observability Failures`
1564
+ - Snapshot timeout
1565
+ - Dashboard render errors
1566
+ - Log sink configuration failure
1567
+
1568
+ ### 14.2 Recovery Behavior
1569
+
1570
+ - Dispatch validation failures:
1571
+ - Skip new dispatches.
1572
+ - Keep service alive.
1573
+ - Continue reconciliation where possible.
1574
+
1575
+ - Worker failures:
1576
+ - Convert to retries with exponential backoff.
1577
+
1578
+ - Tracker candidate-fetch failures:
1579
+ - Skip this tick.
1580
+ - Try again on next tick.
1581
+
1582
+ - Reconciliation state-refresh failures:
1583
+ - Keep current workers.
1584
+ - Retry on next tick.
1585
+
1586
+ - Dashboard/log failures:
1587
+ - Do not crash the orchestrator.
1588
+
1589
+ ### 14.3 Partial State Recovery (Restart)
1590
+
1591
+ Current design is intentionally in-memory for scheduler state.
1592
+
1593
+ After restart:
1594
+
1595
+ - No retry timers are restored from prior process memory.
1596
+ - No running sessions are assumed recoverable.
1597
+ - Service recovers by:
1598
+ - startup terminal workspace cleanup
1599
+ - fresh polling of active issues
1600
+ - re-dispatching eligible work
1601
+
1602
+ ### 14.4 Operator Intervention Points
1603
+
1604
+ Operators can control behavior by:
1605
+
1606
+ - Editing `WORKFLOW.md` (prompt and most runtime settings).
1607
+ - `WORKFLOW.md` changes should be detected and re-applied automatically without restart.
1608
+ - Changing issue states in the tracker:
1609
+ - terminal state -> running session is stopped and workspace cleaned when reconciled
1610
+ - non-active state -> running session is stopped without cleanup
1611
+ - Restarting the service for process recovery or deployment (not as the normal path for applying
1612
+ workflow config changes).
1613
+
1614
+ ## 15. Security and Operational Safety
1615
+
1616
+ ### 15.1 Trust Boundary Assumption
1617
+
1618
+ Each implementation defines its own trust boundary.
1619
+
1620
+ Operational safety requirements:
1621
+
1622
+ - Implementations should state clearly whether they are intended for trusted environments, more
1623
+ restrictive environments, or both.
1624
+ - Implementations should state clearly whether they rely on auto-approved actions, operator
1625
+ approvals, stricter sandboxing, or some combination of those controls.
1626
+ - Workspace isolation and path validation are important baseline controls, but they are not a
1627
+ substitute for whatever approval and sandbox policy an implementation chooses.
1628
+
1629
+ ### 15.2 Filesystem Safety Requirements
1630
+
1631
+ Mandatory:
1632
+
1633
+ - Workspace path must remain under configured workspace root.
1634
+ - Coding-agent cwd must be the per-issue workspace path for the current run.
1635
+ - Workspace directory names must use sanitized identifiers.
1636
+
1637
+ Recommended additional hardening for ports:
1638
+
1639
+ - Run under a dedicated OS user.
1640
+ - Restrict workspace root permissions.
1641
+ - Mount workspace root on a dedicated volume if possible.
1642
+
1643
+ ### 15.3 Secret Handling
1644
+
1645
+ - Support `$VAR` indirection in workflow config.
1646
+ - Do not log API tokens or secret env values.
1647
+ - Validate presence of secrets without printing them.
1648
+
1649
+ ### 15.4 Hook Script Safety
1650
+
1651
+ Workspace hooks are arbitrary shell scripts from `WORKFLOW.md`.
1652
+
1653
+ Implications:
1654
+
1655
+ - Hooks are fully trusted configuration.
1656
+ - Hooks run inside the workspace directory.
1657
+ - Hook output should be truncated in logs.
1658
+ - Hook timeouts are required to avoid hanging the orchestrator.
1659
+
1660
+ ### 15.5 Harness Hardening Guidance
1661
+
1662
+ Running Codex agents against repositories, issue trackers, and other inputs that may contain
1663
+ sensitive data or externally-controlled content can be dangerous. A permissive deployment can lead
1664
+ to data leaks, destructive mutations, or full machine compromise if the agent is induced to execute
1665
+ harmful commands or use overly-powerful integrations.
1666
+
1667
+ Implementations should explicitly evaluate their own risk profile and harden the execution harness
1668
+ where appropriate. This specification intentionally does not mandate a single hardening posture, but
1669
+ ports should not assume that tracker data, repository contents, prompt inputs, or tool arguments are
1670
+ fully trustworthy just because they originate inside a normal workflow.
1671
+
1672
+ Possible hardening measures include:
1673
+
1674
+ - Tightening Codex approval and sandbox settings described elsewhere in this specification instead
1675
+ of running with a maximally permissive configuration.
1676
+ - Adding external isolation layers such as OS/container/VM sandboxing, network restrictions, or
1677
+ separate credentials beyond the built-in Codex policy controls.
1678
+ - Filtering which Linear issues, projects, teams, labels, or other tracker sources are eligible for
1679
+ dispatch so untrusted or out-of-scope tasks do not automatically reach the agent.
1680
+ - Narrowing the optional `linear_graphql` tool so it can only read or mutate data inside the
1681
+ intended project scope, rather than exposing general workspace-wide tracker access.
1682
+ - Reducing the set of client-side tools, credentials, filesystem paths, and network destinations
1683
+ available to the agent to the minimum needed for the workflow.
1684
+
1685
+ The correct controls are deployment-specific, but implementations should document them clearly and
1686
+ treat harness hardening as part of the core safety model rather than an optional afterthought.
1687
+
1688
+ ## 16. Reference Algorithms (Language-Agnostic)
1689
+
1690
+ ### 16.1 Service Startup
1691
+
1692
+ ```text
1693
+ function start_service():
1694
+ configure_logging()
1695
+ start_observability_outputs()
1696
+ start_workflow_watch(on_change=reload_and_reapply_workflow)
1697
+
1698
+ state = {
1699
+ poll_interval_ms: get_config_poll_interval_ms(),
1700
+ max_concurrent_agents: get_config_max_concurrent_agents(),
1701
+ running: {},
1702
+ claimed: set(),
1703
+ retry_attempts: {},
1704
+ completed: set(),
1705
+ codex_totals: {input_tokens: 0, output_tokens: 0, total_tokens: 0, seconds_running: 0},
1706
+ codex_rate_limits: null
1707
+ }
1708
+
1709
+ validation = validate_dispatch_config()
1710
+ if validation is not ok:
1711
+ log_validation_error(validation)
1712
+ fail_startup(validation)
1713
+
1714
+ startup_terminal_workspace_cleanup()
1715
+ schedule_tick(delay_ms=0)
1716
+
1717
+ event_loop(state)
1718
+ ```
1719
+
1720
+ ### 16.2 Poll-and-Dispatch Tick
1721
+
1722
+ ```text
1723
+ on_tick(state):
1724
+ state = reconcile_running_issues(state)
1725
+
1726
+ validation = validate_dispatch_config()
1727
+ if validation is not ok:
1728
+ log_validation_error(validation)
1729
+ notify_observers()
1730
+ schedule_tick(state.poll_interval_ms)
1731
+ return state
1732
+
1733
+ issues = tracker.fetch_candidate_issues()
1734
+ if issues failed:
1735
+ log_tracker_error()
1736
+ notify_observers()
1737
+ schedule_tick(state.poll_interval_ms)
1738
+ return state
1739
+
1740
+ for issue in sort_for_dispatch(issues):
1741
+ if no_available_slots(state):
1742
+ break
1743
+
1744
+ if should_dispatch(issue, state):
1745
+ state = dispatch_issue(issue, state, attempt=null)
1746
+
1747
+ notify_observers()
1748
+ schedule_tick(state.poll_interval_ms)
1749
+ return state
1750
+ ```
1751
+
1752
+ ### 16.3 Reconcile Active Runs
1753
+
1754
+ ```text
1755
+ function reconcile_running_issues(state):
1756
+ state = reconcile_stalled_runs(state)
1757
+
1758
+ running_ids = keys(state.running)
1759
+ if running_ids is empty:
1760
+ return state
1761
+
1762
+ refreshed = tracker.fetch_issue_states_by_ids(running_ids)
1763
+ if refreshed failed:
1764
+ log_debug("keep workers running")
1765
+ return state
1766
+
1767
+ for issue in refreshed:
1768
+ if issue.state in terminal_states:
1769
+ state = terminate_running_issue(state, issue.id, cleanup_workspace=true)
1770
+ else if issue.state in active_states:
1771
+ state.running[issue.id].issue = issue
1772
+ else:
1773
+ state = terminate_running_issue(state, issue.id, cleanup_workspace=false)
1774
+
1775
+ return state
1776
+ ```
1777
+
1778
+ ### 16.4 Dispatch One Issue
1779
+
1780
+ ```text
1781
+ function dispatch_issue(issue, state, attempt):
1782
+ worker = spawn_worker(
1783
+ fn -> run_agent_attempt(issue, attempt, parent_orchestrator_pid) end
1784
+ )
1785
+
1786
+ if worker spawn failed:
1787
+ return schedule_retry(state, issue.id, next_attempt(attempt), {
1788
+ identifier: issue.identifier,
1789
+ error: "failed to spawn agent"
1790
+ })
1791
+
1792
+ state.running[issue.id] = {
1793
+ worker_handle,
1794
+ monitor_handle,
1795
+ identifier: issue.identifier,
1796
+ issue,
1797
+ session_id: null,
1798
+ codex_app_server_pid: null,
1799
+ last_codex_message: null,
1800
+ last_codex_event: null,
1801
+ last_codex_timestamp: null,
1802
+ codex_input_tokens: 0,
1803
+ codex_output_tokens: 0,
1804
+ codex_total_tokens: 0,
1805
+ last_reported_input_tokens: 0,
1806
+ last_reported_output_tokens: 0,
1807
+ last_reported_total_tokens: 0,
1808
+ retry_attempt: normalize_attempt(attempt),
1809
+ started_at: now_utc()
1810
+ }
1811
+
1812
+ state.claimed.add(issue.id)
1813
+ state.retry_attempts.remove(issue.id)
1814
+ return state
1815
+ ```
1816
+
1817
+ ### 16.5 Worker Attempt (Workspace + Prompt + Agent)
1818
+
1819
+ ```text
1820
+ function run_agent_attempt(issue, attempt, orchestrator_channel):
1821
+ workspace = workspace_manager.create_for_issue(issue.identifier)
1822
+ if workspace failed:
1823
+ fail_worker("workspace error")
1824
+
1825
+ if run_hook("before_run", workspace.path) failed:
1826
+ fail_worker("before_run hook error")
1827
+
1828
+ session = app_server.start_session(workspace=workspace.path)
1829
+ if session failed:
1830
+ run_hook_best_effort("after_run", workspace.path)
1831
+ fail_worker("agent session startup error")
1832
+
1833
+ max_turns = config.agent.max_turns
1834
+ turn_number = 1
1835
+
1836
+ while true:
1837
+ prompt = build_turn_prompt(workflow_template, issue, attempt, turn_number, max_turns)
1838
+ if prompt failed:
1839
+ app_server.stop_session(session)
1840
+ run_hook_best_effort("after_run", workspace.path)
1841
+ fail_worker("prompt error")
1842
+
1843
+ turn_result = app_server.run_turn(
1844
+ session=session,
1845
+ prompt=prompt,
1846
+ issue=issue,
1847
+ on_message=(msg) -> send(orchestrator_channel, {codex_update, issue.id, msg})
1848
+ )
1849
+
1850
+ if turn_result failed:
1851
+ app_server.stop_session(session)
1852
+ run_hook_best_effort("after_run", workspace.path)
1853
+ fail_worker("agent turn error")
1854
+
1855
+ refreshed_issue = tracker.fetch_issue_states_by_ids([issue.id])
1856
+ if refreshed_issue failed:
1857
+ app_server.stop_session(session)
1858
+ run_hook_best_effort("after_run", workspace.path)
1859
+ fail_worker("issue state refresh error")
1860
+
1861
+ issue = refreshed_issue[0] or issue
1862
+
1863
+ if issue.state is not active:
1864
+ break
1865
+
1866
+ if turn_number >= max_turns:
1867
+ break
1868
+
1869
+ turn_number = turn_number + 1
1870
+
1871
+ app_server.stop_session(session)
1872
+ run_hook_best_effort("after_run", workspace.path)
1873
+
1874
+ exit_normal()
1875
+ ```
1876
+
1877
+ ### 16.6 Worker Exit and Retry Handling
1878
+
1879
+ ```text
1880
+ on_worker_exit(issue_id, reason, state):
1881
+ running_entry = state.running.remove(issue_id)
1882
+ state = add_runtime_seconds_to_totals(state, running_entry)
1883
+
1884
+ if reason == normal:
1885
+ state.completed.add(issue_id) # bookkeeping only
1886
+ state = schedule_retry(state, issue_id, 1, {
1887
+ identifier: running_entry.identifier,
1888
+ delay_type: continuation
1889
+ })
1890
+ else:
1891
+ state = schedule_retry(state, issue_id, next_attempt_from(running_entry), {
1892
+ identifier: running_entry.identifier,
1893
+ error: format("worker exited: %reason")
1894
+ })
1895
+
1896
+ notify_observers()
1897
+ return state
1898
+ ```
1899
+
1900
+ ```text
1901
+ on_retry_timer(issue_id, state):
1902
+ retry_entry = state.retry_attempts.pop(issue_id)
1903
+ if missing:
1904
+ return state
1905
+
1906
+ candidates = tracker.fetch_candidate_issues()
1907
+ if fetch failed:
1908
+ return schedule_retry(state, issue_id, retry_entry.attempt + 1, {
1909
+ identifier: retry_entry.identifier,
1910
+ error: "retry poll failed"
1911
+ })
1912
+
1913
+ issue = find_by_id(candidates, issue_id)
1914
+ if issue is null:
1915
+ state.claimed.remove(issue_id)
1916
+ return state
1917
+
1918
+ if available_slots(state) == 0:
1919
+ return schedule_retry(state, issue_id, retry_entry.attempt + 1, {
1920
+ identifier: issue.identifier,
1921
+ error: "no available orchestrator slots"
1922
+ })
1923
+
1924
+ return dispatch_issue(issue, state, attempt=retry_entry.attempt)
1925
+ ```
1926
+
1927
+ ## 17. Test and Validation Matrix
1928
+
1929
+ A conforming implementation should include tests that cover the behaviors defined in this
1930
+ specification.
1931
+
1932
+ Validation profiles:
1933
+
1934
+ - `Core Conformance`: deterministic tests required for all conforming implementations.
1935
+ - `Extension Conformance`: required only for optional features that an implementation chooses to
1936
+ ship.
1937
+ - `Real Integration Profile`: environment-dependent smoke/integration checks recommended before
1938
+ production use.
1939
+
1940
+ Unless otherwise noted, Sections 17.1 through 17.7 are `Core Conformance`. Bullets that begin with
1941
+ `If ... is implemented` are `Extension Conformance`.
1942
+
1943
+ ### 17.1 Workflow and Config Parsing
1944
+
1945
+ - Workflow file path precedence:
1946
+ - explicit runtime path is used when provided
1947
+ - cwd default is `WORKFLOW.md` when no explicit runtime path is provided
1948
+ - Workflow file changes are detected and trigger re-read/re-apply without restart
1949
+ - Invalid workflow reload keeps last known good effective configuration and emits an
1950
+ operator-visible error
1951
+ - Missing `WORKFLOW.md` returns typed error
1952
+ - Invalid YAML front matter returns typed error
1953
+ - Front matter non-map returns typed error
1954
+ - Config defaults apply when optional values are missing
1955
+ - `tracker.kind` validation enforces currently supported kind (`linear`)
1956
+ - `tracker.api_key` works (including `$VAR` indirection)
1957
+ - `$VAR` resolution works for tracker API key and path values
1958
+ - `~` path expansion works
1959
+ - `codex.command` is preserved as a shell command string
1960
+ - Per-state concurrency override map normalizes state names and ignores invalid values
1961
+ - Prompt template renders `issue` and `attempt`
1962
+ - Prompt rendering fails on unknown variables (strict mode)
1963
+
1964
+ ### 17.2 Workspace Manager and Safety
1965
+
1966
+ - Deterministic workspace path per issue identifier
1967
+ - Missing workspace directory is created
1968
+ - Existing workspace directory is reused
1969
+ - Existing non-directory path at workspace location is handled safely (replace or fail per
1970
+ implementation policy)
1971
+ - Optional workspace population/synchronization errors are surfaced
1972
+ - Temporary artifacts (`tmp`, `.elixir_ls`) are removed during prep
1973
+ - `after_create` hook runs only on new workspace creation
1974
+ - `before_run` hook runs before each attempt and failure/timeouts abort the current attempt
1975
+ - `after_run` hook runs after each attempt and failure/timeouts are logged and ignored
1976
+ - `before_remove` hook runs on cleanup and failures/timeouts are ignored
1977
+ - Workspace path sanitization and root containment invariants are enforced before agent launch
1978
+ - Agent launch uses the per-issue workspace path as cwd and rejects out-of-root paths
1979
+
1980
+ ### 17.3 Issue Tracker Client
1981
+
1982
+ - Candidate issue fetch uses active states and project slug
1983
+ - Linear query uses the specified project filter field (`slugId`)
1984
+ - Empty `fetch_issues_by_states([])` returns empty without API call
1985
+ - Pagination preserves order across multiple pages
1986
+ - Blockers are normalized from inverse relations of type `blocks`
1987
+ - Labels are normalized to lowercase
1988
+ - Issue state refresh by ID returns minimal normalized issues
1989
+ - Issue state refresh query uses GraphQL ID typing (`[ID!]`) as specified in Section 11.2
1990
+ - Error mapping for request errors, non-200, GraphQL errors, malformed payloads
1991
+
1992
+ ### 17.4 Orchestrator Dispatch, Reconciliation, and Retry
1993
+
1994
+ - Dispatch sort order is priority then oldest creation time
1995
+ - `Todo` issue with non-terminal blockers is not eligible
1996
+ - `Todo` issue with terminal blockers is eligible
1997
+ - Active-state issue refresh updates running entry state
1998
+ - Non-active state stops running agent without workspace cleanup
1999
+ - Terminal state stops running agent and cleans workspace
2000
+ - Reconciliation with no running issues is a no-op
2001
+ - Normal worker exit schedules a short continuation retry (attempt 1)
2002
+ - Abnormal worker exit increments retries with 10s-based exponential backoff
2003
+ - Retry backoff cap uses configured `agent.max_retry_backoff_ms`
2004
+ - Retry queue entries include attempt, due time, identifier, and error
2005
+ - Stall detection kills stalled sessions and schedules retry
2006
+ - Slot exhaustion requeues retries with explicit error reason
2007
+ - If a snapshot API is implemented, it returns running rows, retry rows, token totals, and rate
2008
+ limits
2009
+ - If a snapshot API is implemented, timeout/unavailable cases are surfaced
2010
+
2011
+ ### 17.5 Coding-Agent App-Server Client
2012
+
2013
+ - Launch command uses workspace cwd and invokes `bash -lc <codex.command>`
2014
+ - Startup handshake sends `initialize`, `initialized`, `thread/start`, `turn/start`
2015
+ - `initialize` includes client identity/capabilities payload required by the targeted Codex
2016
+ app-server protocol
2017
+ - Policy-related startup payloads use the implementation's documented approval/sandbox settings
2018
+ - `thread/start` and `turn/start` parse nested IDs and emit `session_started`
2019
+ - Request/response read timeout is enforced
2020
+ - Turn timeout is enforced
2021
+ - Partial JSON lines are buffered until newline
2022
+ - Stdout and stderr are handled separately; protocol JSON is parsed from stdout only
2023
+ - Non-JSON stderr lines are logged but do not crash parsing
2024
+ - Command/file-change approvals are handled according to the implementation's documented policy
2025
+ - Unsupported dynamic tool calls are rejected without stalling the session
2026
+ - User input requests are handled according to the implementation's documented policy and do not
2027
+ stall indefinitely
2028
+ - Usage and rate-limit payloads are extracted from nested payload shapes
2029
+ - Compatible payload variants for approvals, user-input-required signals, and usage/rate-limit
2030
+ telemetry are accepted when they preserve the same logical meaning
2031
+ - If optional client-side tools are implemented, the startup handshake advertises the supported tool
2032
+ specs required for discovery by the targeted app-server version
2033
+ - If the optional `linear_graphql` client-side tool extension is implemented:
2034
+ - the tool is advertised to the session
2035
+ - valid `query` / `variables` inputs execute against configured Linear auth
2036
+ - top-level GraphQL `errors` produce `success=false` while preserving the GraphQL body
2037
+ - invalid arguments, missing auth, and transport failures return structured failure payloads
2038
+ - unsupported tool names still fail without stalling the session
2039
+
2040
+ ### 17.6 Observability
2041
+
2042
+ - Validation failures are operator-visible
2043
+ - Structured logging includes issue/session context fields
2044
+ - Logging sink failures do not crash orchestration
2045
+ - Token/rate-limit aggregation remains correct across repeated agent updates
2046
+ - If a human-readable status surface is implemented, it is driven from orchestrator state and does
2047
+ not affect correctness
2048
+ - If humanized event summaries are implemented, they cover key wrapper/agent event classes without
2049
+ changing orchestrator behavior
2050
+
2051
+ ### 17.7 CLI and Host Lifecycle
2052
+
2053
+ - CLI accepts an optional positional workflow path argument (`path-to-WORKFLOW.md`)
2054
+ - CLI uses `./WORKFLOW.md` when no workflow path argument is provided
2055
+ - CLI errors on nonexistent explicit workflow path or missing default `./WORKFLOW.md`
2056
+ - CLI surfaces startup failure cleanly
2057
+ - CLI exits with success when application starts and shuts down normally
2058
+ - CLI exits nonzero when startup fails or the host process exits abnormally
2059
+
2060
+ ### 17.8 Real Integration Profile (Recommended)
2061
+
2062
+ These checks are recommended for production readiness and may be skipped in CI when credentials,
2063
+ network access, or external service permissions are unavailable.
2064
+
2065
+ - A real tracker smoke test can be run with valid credentials supplied by `LINEAR_API_KEY` or a
2066
+ documented local bootstrap mechanism (for example `~/.linear_api_key`).
2067
+ - Real integration tests should use isolated test identifiers/workspaces and clean up tracker
2068
+ artifacts when practical.
2069
+ - A skipped real-integration test should be reported as skipped, not silently treated as passed.
2070
+ - If a real-integration profile is explicitly enabled in CI or release validation, failures should
2071
+ fail that job.
2072
+
2073
+ ## 18. Implementation Checklist (Definition of Done)
2074
+
2075
+ Use the same validation profiles as Section 17:
2076
+
2077
+ - Section 18.1 = `Core Conformance`
2078
+ - Section 18.2 = `Extension Conformance`
2079
+ - Section 18.3 = `Real Integration Profile`
2080
+
2081
+ ### 18.1 Required for Conformance
2082
+
2083
+ - Workflow path selection supports explicit runtime path and cwd default
2084
+ - `WORKFLOW.md` loader with YAML front matter + prompt body split
2085
+ - Typed config layer with defaults and `$` resolution
2086
+ - Dynamic `WORKFLOW.md` watch/reload/re-apply for config and prompt
2087
+ - Polling orchestrator with single-authority mutable state
2088
+ - Issue tracker client with candidate fetch + state refresh + terminal fetch
2089
+ - Workspace manager with sanitized per-issue workspaces
2090
+ - Workspace lifecycle hooks (`after_create`, `before_run`, `after_run`, `before_remove`)
2091
+ - Hook timeout config (`hooks.timeout_ms`, default `60000`)
2092
+ - Coding-agent app-server subprocess client with JSON line protocol
2093
+ - Codex launch command config (`codex.command`, default `codex app-server`)
2094
+ - Strict prompt rendering with `issue` and `attempt` variables
2095
+ - Exponential retry queue with continuation retries after normal exit
2096
+ - Configurable retry backoff cap (`agent.max_retry_backoff_ms`, default 5m)
2097
+ - Reconciliation that stops runs on terminal/non-active tracker states
2098
+ - Workspace cleanup for terminal issues (startup sweep + active transition)
2099
+ - Structured logs with `issue_id`, `issue_identifier`, and `session_id`
2100
+ - Operator-visible observability (structured logs; optional snapshot/status surface)
2101
+
2102
+ ### 18.2 Recommended Extensions (Not Required for Conformance)
2103
+
2104
+ - Optional HTTP server honors CLI `--port` over `server.port`, uses a safe default bind host, and
2105
+ exposes the baseline endpoints/error semantics in Section 13.7 if shipped.
2106
+ - Optional `linear_graphql` client-side tool extension exposes raw Linear GraphQL access through the
2107
+ app-server session using configured Symphony auth.
2108
+ - TODO: Persist retry queue and session metadata across process restarts.
2109
+ - TODO: Make observability settings configurable in workflow front matter without prescribing UI
2110
+ implementation details.
2111
+ - TODO: Add first-class tracker write APIs (comments/state transitions) in the orchestrator instead
2112
+ of only via agent tools.
2113
+ - TODO: Add pluggable issue tracker adapters beyond Linear.
2114
+
2115
+ ### 18.3 Operational Validation Before Production (Recommended)
2116
+
2117
+ - Run the `Real Integration Profile` from Section 17.8 with valid credentials and network access.
2118
+ - Verify hook execution and workflow path resolution on the target host OS/shell environment.
2119
+ - If the optional HTTP server is shipped, verify the configured port behavior and loopback/default
2120
+ bind expectations on the target environment.
2121
+
2122
+ ## Appendix A. SSH Worker Extension (Optional)
2123
+
2124
+ This appendix describes a common extension profile in which Symphony keeps one central
2125
+ orchestrator but executes worker runs on one or more remote hosts over SSH.
2126
+
2127
+ ### A.1 Execution Model
2128
+
2129
+ - The orchestrator remains the single source of truth for polling, claims, retries, and
2130
+ reconciliation.
2131
+ - `worker.ssh_hosts` provides the candidate SSH destinations for remote execution.
2132
+ - Each worker run is assigned to one host at a time, and that host becomes part of the run's
2133
+ effective execution identity along with the issue workspace.
2134
+ - `workspace.root` is interpreted on the remote host, not on the orchestrator host.
2135
+ - The coding-agent app-server is launched over SSH stdio instead of as a local subprocess, so the
2136
+ orchestrator still owns the session lifecycle even though commands execute remotely.
2137
+ - Continuation turns inside one worker lifetime should stay on the same host and workspace.
2138
+ - A remote host should satisfy the same basic contract as a local worker environment: reachable
2139
+ shell, writable workspace root, coding-agent executable, and any required auth or repository
2140
+ prerequisites.
2141
+
2142
+ ### A.2 Scheduling Notes
2143
+
2144
+ - SSH hosts may be treated as a pool for dispatch.
2145
+ - Implementations may prefer the previously used host on retries when that host is still
2146
+ available.
2147
+ - `worker.max_concurrent_agents_per_host` is an optional shared per-host cap across configured SSH
2148
+ hosts.
2149
+ - When all SSH hosts are at capacity, dispatch should wait rather than silently falling back to a
2150
+ different execution mode.
2151
+ - Implementations may fail over to another host when the original host is unavailable before work
2152
+ has meaningfully started.
2153
+ - Once a run has already produced side effects, a transparent rerun on another host should be
2154
+ treated as a new attempt, not as invisible failover.
2155
+
2156
+ ### A.3 Problems to Consider
2157
+
2158
+ - Remote environment drift:
2159
+ - Each host needs the expected shell environment, coding-agent executable, auth, and repository
2160
+ prerequisites.
2161
+ - Workspace locality:
2162
+ - Workspaces are usually host-local, so moving an issue to a different host is typically a cold
2163
+ restart unless shared storage exists.
2164
+ - Path and command safety:
2165
+ - Remote path resolution, shell quoting, and workspace-boundary checks matter more once execution
2166
+ crosses a machine boundary.
2167
+ - Startup and failover semantics:
2168
+ - Implementations should distinguish host-connectivity/startup failures from in-workspace agent
2169
+ failures so the same ticket is not accidentally re-executed on multiple hosts.
2170
+ - Host health and saturation:
2171
+ - A dead or overloaded host should reduce available capacity, not cause duplicate execution or an
2172
+ accidental fallback to local work.
2173
+ - Cleanup and observability:
2174
+ - Operators need to know which host owns a run, where its workspace lives, and whether cleanup
2175
+ happened on the right machine.