lorenz 0.1.0 → 0.1.2

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 (205) hide show
  1. package/README.md +83 -691
  2. package/RELEASE-MANIFEST.json +2 -2
  3. package/apps/cli/dist/daemon.d.ts +1 -1
  4. package/apps/cli/dist/daemon.d.ts.map +1 -1
  5. package/apps/cli/dist/daemon.js +6 -13
  6. package/apps/cli/dist/daemon.js.map +1 -1
  7. package/apps/cli/dist/doctor.js +5 -5
  8. package/apps/cli/dist/doctor.js.map +1 -1
  9. package/apps/cli/dist/index.d.ts +2 -2
  10. package/apps/cli/dist/index.d.ts.map +1 -1
  11. package/apps/cli/dist/index.js +1 -1
  12. package/apps/cli/dist/index.js.map +1 -1
  13. package/apps/cli/dist/main.js +5 -5
  14. package/apps/cli/dist/main.js.map +1 -1
  15. package/apps/cli/dist/runs.js +2 -2
  16. package/apps/cli/dist/runs.js.map +1 -1
  17. package/apps/cli/dist/workerDriverLoader.js +1 -1
  18. package/apps/cli/package.json +1 -1
  19. package/apps/{symphony-dashboard/dist/assets/index-DQ6XlL0d.js → lorenz-dashboard/dist/assets/index-DD4yVBDF.js} +13 -13
  20. package/apps/{symphony-dashboard → lorenz-dashboard}/dist/index.html +2 -2
  21. package/extensions/docker-worker/dist/index.d.ts +2 -2
  22. package/extensions/docker-worker/dist/index.js +4 -4
  23. package/extensions/docker-worker/dist/index.js.map +1 -1
  24. package/extensions/docker-worker/package.json +1 -1
  25. package/extensions/jira-tracker/dist/client.d.ts +7 -2
  26. package/extensions/jira-tracker/dist/client.d.ts.map +1 -1
  27. package/extensions/jira-tracker/dist/client.js +163 -6
  28. package/extensions/jira-tracker/dist/client.js.map +1 -1
  29. package/extensions/jira-tracker/dist/options.d.ts +4 -2
  30. package/extensions/jira-tracker/dist/options.d.ts.map +1 -1
  31. package/extensions/jira-tracker/dist/options.js +2 -0
  32. package/extensions/jira-tracker/dist/options.js.map +1 -1
  33. package/extensions/jira-tracker/dist/provider.d.ts.map +1 -1
  34. package/extensions/jira-tracker/dist/provider.js +6 -0
  35. package/extensions/jira-tracker/dist/provider.js.map +1 -1
  36. package/extensions/jira-tracker/package.json +1 -1
  37. package/extensions/linear-tracker/dist/client.js +9 -9
  38. package/extensions/linear-tracker/dist/client.js.map +1 -1
  39. package/extensions/linear-tracker/dist/toolOps.d.ts.map +1 -1
  40. package/extensions/linear-tracker/dist/toolOps.js +101 -10
  41. package/extensions/linear-tracker/dist/toolOps.js.map +1 -1
  42. package/extensions/linear-tracker/dist/tools.js +5 -5
  43. package/extensions/linear-tracker/dist/tools.js.map +1 -1
  44. package/extensions/linear-tracker/package.json +1 -1
  45. package/extensions/local-tracker/dist/boardStore.d.ts +1 -1
  46. package/extensions/local-tracker/dist/boardStore.js +5 -5
  47. package/extensions/local-tracker/dist/boardStore.js.map +1 -1
  48. package/extensions/local-tracker/dist/options.d.ts +1 -1
  49. package/extensions/local-tracker/dist/options.d.ts.map +1 -1
  50. package/extensions/local-tracker/dist/resolveBoardDir.d.ts +2 -2
  51. package/extensions/local-tracker/dist/resolveBoardDir.d.ts.map +1 -1
  52. package/extensions/local-tracker/dist/resolveBoardDir.js +2 -2
  53. package/extensions/local-tracker/dist/resolveBoardDir.js.map +1 -1
  54. package/extensions/local-tracker/package.json +1 -1
  55. package/extensions/memory-tracker/dist/index.d.ts +1 -1
  56. package/extensions/memory-tracker/dist/index.js +3 -3
  57. package/extensions/memory-tracker/dist/index.js.map +1 -1
  58. package/extensions/memory-tracker/package.json +1 -1
  59. package/extensions/slack-tracker/dist/inMemoryTransport.d.ts +4 -1
  60. package/extensions/slack-tracker/dist/inMemoryTransport.d.ts.map +1 -1
  61. package/extensions/slack-tracker/dist/inMemoryTransport.js +4 -2
  62. package/extensions/slack-tracker/dist/inMemoryTransport.js.map +1 -1
  63. package/extensions/slack-tracker/dist/index.d.ts +1 -1
  64. package/extensions/slack-tracker/dist/index.d.ts.map +1 -1
  65. package/extensions/slack-tracker/dist/index.js +1 -1
  66. package/extensions/slack-tracker/dist/index.js.map +1 -1
  67. package/extensions/slack-tracker/dist/mapping.d.ts +9 -0
  68. package/extensions/slack-tracker/dist/mapping.d.ts.map +1 -1
  69. package/extensions/slack-tracker/dist/mapping.js +13 -0
  70. package/extensions/slack-tracker/dist/mapping.js.map +1 -1
  71. package/extensions/slack-tracker/dist/operations.d.ts.map +1 -1
  72. package/extensions/slack-tracker/dist/operations.js +8 -4
  73. package/extensions/slack-tracker/dist/operations.js.map +1 -1
  74. package/extensions/slack-tracker/dist/options.d.ts +13 -1
  75. package/extensions/slack-tracker/dist/options.d.ts.map +1 -1
  76. package/extensions/slack-tracker/dist/options.js +1 -0
  77. package/extensions/slack-tracker/dist/options.js.map +1 -1
  78. package/extensions/slack-tracker/dist/provider.d.ts.map +1 -1
  79. package/extensions/slack-tracker/dist/provider.js +8 -1
  80. package/extensions/slack-tracker/dist/provider.js.map +1 -1
  81. package/extensions/slack-tracker/dist/threadState.d.ts.map +1 -1
  82. package/extensions/slack-tracker/dist/threadState.js +8 -4
  83. package/extensions/slack-tracker/dist/threadState.js.map +1 -1
  84. package/extensions/slack-tracker/dist/webTransport.d.ts +1 -0
  85. package/extensions/slack-tracker/dist/webTransport.d.ts.map +1 -1
  86. package/extensions/slack-tracker/dist/webTransport.js +10 -4
  87. package/extensions/slack-tracker/dist/webTransport.js.map +1 -1
  88. package/extensions/slack-tracker/package.json +1 -1
  89. package/package.json +3 -3
  90. package/packages/acp/dist/index.d.ts +1 -1
  91. package/packages/acp/dist/index.d.ts.map +1 -1
  92. package/packages/acp/dist/index.js +3 -8
  93. package/packages/acp/dist/index.js.map +1 -1
  94. package/packages/acp/dist/options.d.ts +2 -2
  95. package/packages/acp/dist/options.d.ts.map +1 -1
  96. package/packages/acp/package.json +1 -1
  97. package/packages/agent-runner/package.json +1 -1
  98. package/packages/agent-sdk/dist/provider.d.ts +1 -8
  99. package/packages/agent-sdk/dist/provider.d.ts.map +1 -1
  100. package/packages/agent-sdk/dist/provider.js.map +1 -1
  101. package/packages/agent-sdk/package.json +1 -1
  102. package/packages/cli-kit/package.json +1 -1
  103. package/packages/config/dist/defaults.js +4 -4
  104. package/packages/config/dist/defaults.js.map +1 -1
  105. package/packages/config/dist/parse.js +1 -1
  106. package/packages/config/dist/parse.js.map +1 -1
  107. package/packages/config/dist/schemas.d.ts +1 -1
  108. package/packages/config/package.json +1 -1
  109. package/packages/dispatch/package.json +1 -1
  110. package/packages/dispatch-coordinator/package.json +1 -1
  111. package/packages/domain/dist/index.d.ts +8 -8
  112. package/packages/domain/package.json +1 -1
  113. package/packages/humanize/package.json +1 -1
  114. package/packages/issue/package.json +1 -1
  115. package/packages/log-file/dist/index.js +2 -2
  116. package/packages/log-file/dist/index.js.map +1 -1
  117. package/packages/log-file/package.json +1 -1
  118. package/packages/mcp/dist/agentEndpoint.js +2 -2
  119. package/packages/mcp/dist/agentEndpoint.js.map +1 -1
  120. package/packages/mcp/package.json +1 -1
  121. package/packages/orchestrator/package.json +1 -1
  122. package/packages/policies/package.json +1 -1
  123. package/packages/presenter/dist/index.js +1 -1
  124. package/packages/presenter/dist/index.js.map +1 -1
  125. package/packages/presenter/package.json +1 -1
  126. package/packages/projections/package.json +1 -1
  127. package/packages/prompt/package.json +1 -1
  128. package/packages/retry-scheduler/package.json +1 -1
  129. package/packages/runtime/dist/index.d.ts +5 -5
  130. package/packages/runtime/dist/index.d.ts.map +1 -1
  131. package/packages/runtime/dist/index.js +1 -1
  132. package/packages/runtime/dist/index.js.map +1 -1
  133. package/packages/runtime/package.json +1 -1
  134. package/packages/runtime-events/package.json +1 -1
  135. package/packages/server/dist/index.js +1 -1
  136. package/packages/server/dist/index.js.map +1 -1
  137. package/packages/server/dist/issue-store.js +1 -1
  138. package/packages/server/dist/issue-store.js.map +1 -1
  139. package/packages/server/package.json +1 -1
  140. package/packages/ssh/dist/index.js +1 -1
  141. package/packages/ssh/dist/index.js.map +1 -1
  142. package/packages/ssh/package.json +1 -1
  143. package/packages/static-worker/package.json +1 -1
  144. package/packages/tool-sdk/package.json +1 -1
  145. package/packages/traceviz-emitter/package.json +1 -1
  146. package/packages/traceviz-server/package.json +1 -1
  147. package/packages/tracker-sdk/dist/index.d.ts +1 -1
  148. package/packages/tracker-sdk/dist/index.d.ts.map +1 -1
  149. package/packages/tracker-sdk/dist/index.js.map +1 -1
  150. package/packages/tracker-sdk/dist/provider.d.ts +13 -2
  151. package/packages/tracker-sdk/dist/provider.d.ts.map +1 -1
  152. package/packages/tracker-sdk/dist/toolPack.d.ts.map +1 -1
  153. package/packages/tracker-sdk/dist/toolPack.js +43 -3
  154. package/packages/tracker-sdk/dist/toolPack.js.map +1 -1
  155. package/packages/tracker-sdk/package.json +1 -1
  156. package/packages/tui/dist/index.js +1 -1
  157. package/packages/tui/dist/index.js.map +1 -1
  158. package/packages/tui/package.json +1 -1
  159. package/packages/worker-host-pool/package.json +1 -1
  160. package/packages/worker-pool/package.json +1 -1
  161. package/packages/worker-sdk/dist/conformance.d.ts +1 -1
  162. package/packages/worker-sdk/dist/conformance.js +2 -2
  163. package/packages/worker-sdk/dist/conformance.js.map +1 -1
  164. package/packages/worker-sdk/dist/types.d.ts +1 -1
  165. package/packages/worker-sdk/dist/types.d.ts.map +1 -1
  166. package/packages/worker-sdk/dist/types.js +1 -1
  167. package/packages/worker-sdk/dist/types.js.map +1 -1
  168. package/packages/worker-sdk/package.json +1 -1
  169. package/packages/workflow/dist/index.js +1 -1
  170. package/packages/workflow/dist/index.js.map +1 -1
  171. package/packages/workflow/package.json +1 -1
  172. package/packages/workspace/dist/index.d.ts +1 -1
  173. package/packages/workspace/dist/index.d.ts.map +1 -1
  174. package/packages/workspace/dist/index.js +5 -3
  175. package/packages/workspace/dist/index.js.map +1 -1
  176. package/packages/workspace/package.json +1 -1
  177. package/runtime-deps/anthropic-claude-agent-sdk/node_modules/.bin/anthropic-ai-sdk +3 -7
  178. package/vendor/claude-agent-acp/dist/bundle.js +1 -1
  179. package/LICENSE +0 -201
  180. package/NOTICE +0 -13
  181. package/packages/acp/dist/toml.d.ts +0 -2
  182. package/packages/acp/dist/toml.d.ts.map +0 -1
  183. package/packages/acp/dist/toml.js +0 -51
  184. package/packages/acp/dist/toml.js.map +0 -1
  185. package/packages/mcp/dist/filter.d.ts +0 -70
  186. package/packages/mcp/dist/filter.d.ts.map +0 -1
  187. package/packages/mcp/dist/filter.js +0 -231
  188. package/packages/mcp/dist/filter.js.map +0 -1
  189. package/packages/mcp/dist/tools/linear.d.ts +0 -5
  190. package/packages/mcp/dist/tools/linear.d.ts.map +0 -1
  191. package/packages/mcp/dist/tools/linear.js +0 -192
  192. package/packages/mcp/dist/tools/linear.js.map +0 -1
  193. package/packages/mcp/dist/tools/local.d.ts +0 -5
  194. package/packages/mcp/dist/tools/local.d.ts.map +0 -1
  195. package/packages/mcp/dist/tools/local.js +0 -161
  196. package/packages/mcp/dist/tools/local.js.map +0 -1
  197. package/packages/mcp/dist/tools/result.d.ts +0 -5
  198. package/packages/mcp/dist/tools/result.d.ts.map +0 -1
  199. package/packages/mcp/dist/tools/result.js +0 -15
  200. package/packages/mcp/dist/tools/result.js.map +0 -1
  201. package/packages/policies/dist/resume.d.ts +0 -14
  202. package/packages/policies/dist/resume.d.ts.map +0 -1
  203. package/packages/policies/dist/resume.js +0 -7
  204. package/packages/policies/dist/resume.js.map +0 -1
  205. /package/apps/{symphony-dashboard → lorenz-dashboard}/dist/assets/index-B3owF3jd.css +0 -0
package/README.md CHANGED
@@ -1,702 +1,125 @@
1
1
  # Lorenz
2
2
 
3
- Lorenz turns tracker issues into agent runs. It polls for eligible work, prepares a workspace,
4
- renders the workflow prompt with issue context, starts Codex or Claude, and records the run so
5
- operators can inspect state, retries, cost, and logs.
3
+ [![Documentation](https://img.shields.io/badge/docs-ryanlyn.github.io%2Florenz-14b8a6)](https://ryanlyn.github.io/lorenz/)
6
4
 
7
- This workspace owns the TypeScript CLI, runtime packages, tracker adapters, terminal dashboard,
8
- local observability server, trace viewer packages, and tests.
5
+ Originated from [OpenAI Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/), Lorenz lets you declare work on trackers (in-memory, Obsidian markdown files, Linear, Jira, Slack etc.) and manage the dispatch, execution, and convergence of concurrent agent sessions until they reach a specified terminal state. It is harness-agnostic through the [Agent-Client Protocol](https://agentclientprotocol.com/get-started/introduction) with support for local, static SSH boxes, or (experimental) cloud-brokered VMs.
9
6
 
10
- ## Requirements
7
+ ## Screenshots
11
8
 
12
- [mise](https://mise.jdx.dev/) is recommended for managing Node and pnpm:
9
+ Lorenz ships two operator views over the same runtime snapshot: an Ink terminal dashboard (TUI)
10
+ and a web dashboard served by the observability API.
13
11
 
14
- ```sh
15
- mise trust
16
- mise install
17
- pnpm install
18
- ```
12
+ ### Terminal dashboard (TUI)
13
+
14
+ ![Lorenz terminal dashboard](docs/images/lorenz-tui.png)
15
+
16
+ ### Web dashboard
19
17
 
20
- The workspace uses Node 24 and pnpm 9 from `ts/mise.toml`.
18
+ ![Lorenz web dashboard](docs/images/lorenz-dashboard.png)
21
19
 
22
- Runtime requirements depend on the workflow:
20
+ ## Documentation
23
21
 
24
- - `LINEAR_API_KEY` for Linear-backed workflows.
25
- - `codex` on `PATH` for Codex runs and live Codex tests.
26
- - A Claude ACP bridge, usually `claude-agent-acp`, for Claude runs and live Claude tests.
27
- - SSH access for remote workers and live SSH tests.
28
- - Docker and `ssh-keygen` for disposable live SSH workers when no real SSH hosts are configured.
22
+ The published documentation site is at **[ryanlyn.github.io/lorenz](https://ryanlyn.github.io/lorenz/)**, built from [`docs/`](./docs). Start here:
29
23
 
30
- Run commands from `ts/` unless a command says otherwise.
24
+ - [Getting started](./docs/getting-started.md) - install, write a `WORKFLOW.md`, run your first issue.
25
+ - [How it works](./docs/how-it-works.md) - the polling, dispatch, and run lifecycle.
26
+ - [Configuration reference](./docs/reference/configuration.md) - every front-matter key, default, and meaning.
27
+ - [Trackers](./docs/trackers/index.md) - Linear, Jira, Slack, local, and memory sources of issues.
28
+ - [CLI](./docs/cli.md) - commands, flags, and run history.
31
29
 
32
- ## Run
30
+ ## Quickstart
31
+
32
+ Running Lorenz is as easy as:
33
33
 
34
34
  ```sh
35
- pnpm build
36
- pnpm start -- WORKFLOW.md
37
- pnpm start:once -- --dry-run --no-tui WORKFLOW.md
38
- pnpm runs -- --port 4000 --failed
35
+ npx @lorenz WORKFLOW.md
39
36
  ```
40
37
 
41
- The built CLI is `lorenz`:
38
+ with full CLI options:
42
39
 
43
40
  ```sh
44
41
  lorenz [--once] [--dry-run] [--no-tui] [--port <port>] [--logs-root <path>] [path-to-WORKFLOW.md]
45
- lorenz runs [--issue ID] [--failed] [--cost] [--retries] [--id RUN_ID] [--limit N] [--url URL | --port PORT] [--json]
42
+ lorenz runs [--issue ID] [--failed] [--cost] [--retries] [--id RUN_ID] [--limit N] [--json]
46
43
  ```
47
44
 
48
- Optional flags:
49
-
50
- - `--logs-root <path>` writes logs under `<path>/log/symphony.log`.
51
- - `--port <port>` starts the local observability dashboard and JSON API.
52
- - `--once` polls once and exits.
53
- - `--dry-run` evaluates candidates without dispatching agents.
54
- - `--no-tui` disables the terminal dashboard and prints JSON snapshots.
55
-
56
- With no workflow path, the CLI reads `SYMPHONY_WORKFLOW`, then `./WORKFLOW.md`.
57
-
58
- The runtime reloads the workflow before each poll. If startup cannot read or parse the workflow,
59
- the CLI exits with an error. If a later reload fails, the runtime keeps the last good workflow and
60
- records a `workflow_reload_failed` event.
61
-
62
- ## Workspace Layout
63
-
64
- See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) for the layering rules and the tracker extension
65
- contract (including the recipe for adding a new tracker backend).
66
-
67
- - `apps/cli` is the composition root: it invokes the built-in extensions' registration and wires
68
- configuration, agent runners, the runtime, the TUI, and the observability server into the
69
- shipped binary.
70
- - `apps/traceviz` renders trace event streams for local inspection.
71
- - `packages/tracker-sdk` is the extension SDK: the `TrackerProvider` contract, the provider
72
- registry, and the helpers tracker backends build on.
73
- - `extensions/*` are the backend extensions: `linear-tracker`, `local-tracker`,
74
- `memory-tracker`, and `jira-tracker` are self-contained tracker providers (config
75
- parsing, runtime client, tool packs) that each export their own registration; the CLI
76
- invokes the built-in set at its composition root.
77
- - The remaining `packages/*` are the provider-agnostic engine: domain model, configuration
78
- loader, prompt renderer, runtime, policies, MCP server, dashboards, logging, SSH, and
79
- support libraries.
80
- - `test/` contains workspace-level integration, contract, sandbox, and live tests.
81
- - Package- and app-owned unit tests live under `packages/<name>/test/` or `apps/<name>/test/` as
82
- `.test.ts` or `.test.tsx` files.
83
-
84
- Create a package when a boundary has a clear owner. Keep curated exports in `src/index.ts` and
85
- declare internal dependencies as `workspace:*`.
45
+ `--logs-root <path>` writes logs under `<path>/log/lorenz.log`. With no workflow path the CLI reads
46
+ `LORENZ_WORKFLOW`, then `./WORKFLOW.md`. See [CLI](./docs/cli.md) for every flag and command.
47
+
48
+ Runtime needs depend on the workflow: `LINEAR_API_KEY` for Linear, `codex` on `PATH` for Codex
49
+ runs, a Claude ACP bridge for Claude runs, and SSH access for remote workers. See
50
+ [Getting started](./docs/getting-started.md) for the full list. Run commands from the repository
51
+ root unless a command says otherwise.
86
52
 
87
53
  ## Configuration
88
54
 
89
55
  Configuration lives in the YAML front matter of a workflow file. The Markdown body below the front
90
- matter is the agent session prompt, rendered as Liquid with issue context variables.
91
-
92
- ### Quickstart
93
-
94
- ```yaml
95
- ---
96
- tracker:
97
- kind: linear
98
- trackers:
99
- linear:
100
- provider: linear
101
- project_slug: "your-project-slug"
102
- workspace:
103
- root: ~/code/workspaces
104
- hooks:
105
- after_create: |
106
- git clone git@github.com:your-org/your-repo.git .
107
- agent:
108
- kind: codex
109
- ---
110
-
111
- You are working on {{ issue.identifier }}: {{ issue.title }}
112
-
113
- {{ issue.description }}
114
- ```
115
-
116
- Set `LINEAR_API_KEY` in your environment before running a Linear workflow.
56
+ matter is the agent session prompt, rendered as Liquid with issue-context variables. See
57
+ [Workflows](./docs/workflows.md) for the file format and a quickstart example.
117
58
 
118
59
  ### Full Reference
119
60
 
120
- ```yaml
121
- ---
122
- tracker:
123
- kind: linear # linear, jira, jira-mcp, local, or memory
124
- trackers:
125
- linear:
126
- provider: linear
127
- api_key: $LINEAR_API_KEY # defaults to $LINEAR_API_KEY when unset
128
- endpoint: "https://api.linear.app/graphql"
129
- project_slug: "my-project" # right-click a Linear project and copy the URL slug
130
- assignee: $LINEAR_ASSIGNEE # optional; filters issues by assignee
131
- active_states:
132
- - Todo # default: ["Todo", "In Progress"]
133
- - In Progress
134
- - Agent Review
135
- - Merging
136
- - Rework
137
- terminal_states:
138
- - Closed # default: ["Closed", "Cancelled", "Canceled",
139
- - Cancelled # "Duplicate", "Done"]
140
- - Canceled
141
- - Duplicate
142
- - Done
143
- dispatch:
144
- accept_unrouted: true # accept issues without a route label; default: true
145
- only_routes: null # null accepts any route, [] accepts none
146
- route_label_prefix: "Symphony:" # route labels look like "Symphony:backend"
147
-
148
- tools:
149
- local:
150
- path: .symphony/local # explicit extra pack config; not needed for Linear-owned tools
151
-
152
- polling:
153
- interval_ms: 30000 # default: 30000
154
-
155
- workspace:
156
- root: ~/code/workspaces # default: $TMPDIR/symphony_workspaces
157
-
158
- worker:
159
- # Either list static SSH hosts here, or set kind to select a top-level workers.<name> profile.
160
- # kind: static-prod
161
- ssh_hosts:
162
- - worker1.example.com # standard OpenSSH targets and Host aliases work
163
- - worker2.example.com:2222
164
- ssh_timeout_ms: 60000 # default: 60000
165
- max_concurrent_agents_per_host: 2 # optional; defaults to the global agent cap per host
166
- # Alternative to ssh_hosts (mutually exclusive): a warm pool of leased workers
167
- # provisioned by a worker driver. Disabled by default.
168
- worker_pool:
169
- enabled: false
170
- # driver: fake # compatibility fallback when worker.kind is omitted
171
- min: 0 # warm-inventory floor the reaper keeps alive
172
- max: 1 # ceiling on concurrent workers
173
- warm: 1 # pre-warmed idle workers the reaper tops up toward
174
- max_in_flight: 1 # run slots per machine; >1 requires co_residence: true
175
- ttl_ms: 3600000 # hard worker lifetime before recycle
176
- idle_reap_ms: 300000 # idle window before a warm worker above min is reaped
177
- acquire_timeout_ms: 30000 # how long an acquire waits for capacity
178
- spend: # optional caps, all in worker count / wall-clock worker-seconds
179
- max_concurrent_workers: 4
180
- max_worker_seconds: 86400
181
- daily_worker_seconds: 28800
182
-
183
- workers:
184
- static-prod: # selected by worker.kind, options pass through verbatim (snake_case preserved)
185
- driver: static-ssh
186
- ssh_hosts: ["user@worker1:22"]
187
-
188
- agent:
189
- kind: codex # default: "codex"; "claude" is configured below
190
- max_concurrent_agents: 10 # default: 10
191
- max_turns: 20 # default: 20
192
- max_retry_backoff_ms: 300000 # default: 300000
193
- ensemble_size: 1 # default: 1
194
- skills: # skill directories overlaid into each workspace before the agent starts
195
- - ./.codex/skills/symphony-land # one entry per skill directory
196
-
197
- agents:
198
- turn_timeout_ms: 3600000 # default: 3600000
199
- stall_timeout_ms: 300000 # default: 300000
200
- codex:
201
- executor: acp
202
- bridge_command: codex-acp
203
- claude:
204
- executor: acp
205
- bridge_command: claude-agent-acp
206
- bridge_args:
207
- - --permission-mode
208
- - dontAsk
209
- - --model
210
- - claude-opus-4-6[1m]
211
-
212
- status_overrides:
213
- in progress:
214
- agent:
215
- max_concurrent_agents: 5
216
- merging:
217
- agent:
218
- max_concurrent_agents: 2
219
-
220
- codex:
221
- command: codex-acp # legacy alias for agents.codex.bridge_command
222
- turn_timeout_ms: 3600000 # default: 3600000
223
- stall_timeout_ms: 300000 # default: 300000
224
-
225
- claude:
226
- command: claude-agent-acp # ACP bridge command
227
- model: claude-opus-4-6[1m]
228
- permission_mode: dontAsk
229
- strict_mcp_config: true # default: true
230
-
231
- hooks:
232
- after_create: | # runs after a workspace directory is created
233
- git clone --depth 1 git@github.com:org/repo.git .
234
- before_run: | # runs before each agent turn
235
- git pull origin main
236
- after_run: | # best effort; runs after each agent turn
237
- echo "turn complete"
238
- before_remove: | # best effort; runs before workspace cleanup
239
- echo "cleaning up"
240
- timeout_ms: 60000 # default: 60000
241
-
242
- observability:
243
- dashboard_enabled: true # terminal dashboard; default: true
244
- refresh_ms: 1000 # default: 1000
245
- render_interval_ms: 16 # default: 16
246
-
247
- server:
248
- port: 4000 # enables the web dashboard; default: disabled
249
- host: 127.0.0.1 # default: 127.0.0.1
250
-
251
- logging:
252
- log_file: ./log/symphony.log # default: ~/.symphony/log/symphony.log
253
- ---
254
- ```
255
-
256
- Notes:
257
-
258
- - `tracker.kind` is always required. When `trackers` is present it selects a named bundle, and
259
- `trackers.<name>.provider` selects the tracker implementation.
260
- - The older flat `tracker.kind: linear` shape is still accepted when no `trackers` map is present.
261
- - `trackers.linear.project_slug`, `trackers.linear.project_slugs`, or
262
- `trackers.linear.project_labels` is required for Linear workflows.
263
- - `trackers.linear.api_key` falls back to `LINEAR_API_KEY`; `trackers.linear.assignee` falls back
264
- to `LINEAR_ASSIGNEE`.
265
- - Shared tracker secrets can use `op://` references when the 1Password CLI is installed.
266
- - `tools.<pack>` mounts or configures extra tool packs. Tracker-owned tools are implicit, so a
267
- Linear tracker does not need a matching `tools.linear` entry.
268
- - `workspace.root` supports `~` and whole-value `$VAR` expansion. `SYMPHONY_WORKSPACE_ROOT`
269
- overrides `workspace.root` at runtime.
270
- - `SYMPHONY_SSH_CONFIG` points SSH worker commands at a custom OpenSSH config file.
271
- - Hooks run through `bash -lc` locally or over SSH with the workspace as `cwd`. Use
272
- fail-fast shell options in bootstrap hooks so clone and dependency setup failures stop workspace
273
- creation immediately.
274
- - `codex.command` runs through `bash -lc`, so shell expansion happens in the launched process.
275
- - If the Markdown body is blank, Lorenz uses a default prompt with the issue identifier, title,
276
- and body.
61
+ Every front-matter key, its type, verified default, and meaning are in the
62
+ [Configuration reference](./docs/reference/configuration.md). `workspace.root` supports `~` and
63
+ whole-value `$VAR` expansion, and `LORENZ_WORKSPACE_ROOT` overrides it at runtime.
277
64
 
278
65
  ## Linear
279
66
 
280
- Prerequisites:
281
-
282
- 1. Create a personal API token in Linear Settings, Security & access, Personal API keys.
283
- 2. Export it as `LINEAR_API_KEY`, or set `trackers.linear.api_key: $LINEAR_API_KEY`.
284
- 3. Find the project slug by right-clicking a Linear project and copying its URL. The slug is in the
285
- path.
286
- 4. The example workflows use non-standard states such as `Agent Review`, `Rework`, `Human Review`,
287
- and `Merging`. Add those states under Team Settings, Workflow, or adjust `active_states` and
288
- `terminal_states` to match your team.
289
-
290
- Route labels let multiple Lorenz instances share one Linear project. With the default
291
- `route_label_prefix`, labels such as `Symphony:backend` and `Symphony:frontend` become route names.
292
-
293
- ## Trackers
294
-
295
- A tracker is the source of issues Lorenz works on. `tracker.kind` selects a named bundle under
296
- `trackers`, and the selected bundle's `provider` selects the implementation. Every tracker exposes
297
- the same read surface to the runtime (poll for candidate issues, refresh in-flight issues by id)
298
- and a set of agent tools. Those tools are read+write symmetric across kinds, mirroring
299
- `linear_graphql` (which both reads and writes): each tracker gives the agent at least one write tool
300
- and one read tool. The tools differ per kind; their descriptions are self-documenting and surface
301
- to the agent via the MCP `tools/list` call.
302
-
303
- Supported kinds:
304
-
305
- - `linear` - issues live in a Linear project. Read access uses `trackers.linear.api_key` (resolved
306
- from `LINEAR_API_KEY`) and project selection uses `trackers.linear.project_slug`,
307
- `trackers.linear.project_slugs`, or `trackers.linear.project_labels`. Agents can use
308
- provider-neutral `tracker_*` tools or the legacy
309
- `linear_graphql` tool.
310
- - `jira` - issues live in Jira Cloud and are accessed directly over Jira REST. Configure
311
- `trackers.jira.base_url`, `trackers.jira.email`, `trackers.jira.api_key`, and either
312
- `trackers.jira.project_keys` or `trackers.jira.jql`. `JIRA_BASE_URL`, `JIRA_EMAIL`, and
313
- `JIRA_API_KEY` are used as fallbacks.
314
- - `jira-mcp` - issues live in Jira, but Lorenz reaches them through an external MCP server.
315
- Configure `trackers.jira-mcp.mcp.url` and either `trackers.jira-mcp.project_keys` or
316
- `trackers.jira-mcp.jql`. Tool names can be overridden under `trackers.jira-mcp.mcp.tools`.
317
- - `local` - issues live as Markdown files on disk. No external service required.
318
- - `slack` - an @-mention of the bot (in a channel message or a thread reply) is an issue, the
319
- thread carries the status (`@bot !` commands and bot `status:` replies), and a thread reply is
320
- a comment.
321
- - `memory` - an in-process tracker used for tests and dry runs.
322
-
323
- All non-memory providers expose the provider-neutral agent tools:
324
-
325
- - `tracker_read_issue`
326
- - `tracker_query`
327
- - `tracker_update_status`
328
- - `tracker_comment`
329
- - `tracker_create_issue`
330
-
331
- Provider-specific tools are compatibility escape hatches, not the preferred workflow contract.
332
-
333
- All kinds share the dispatch routing block under the selected tracker bundle:
334
-
335
- ```yaml
336
- tracker:
337
- kind: linear
338
- trackers:
339
- linear:
340
- provider: linear
341
- dispatch:
342
- accept_unrouted: true # process issues that carry no matching route label (default)
343
- only_routes: null # or a list of route names this instance handles
344
- route_label_prefix: "Symphony:" # the label prefix that names a route
345
- ```
346
-
347
- ### Jira tracker
348
-
349
- For both `jira` and `jira-mcp`, Lorenz only picks up issues that are assigned to the configured
350
- user (`trackers.jira.assignee` or `trackers.jira-mcp.assignee`, defaulting to the authenticated
351
- user via `assignee = currentUser()`) and labeled `agent`. This holds even when the configured JQL
352
- widens the scope, so issues must be explicitly delegated before Lorenz will dispatch them.
353
- Jira REST issues created through `tracker_create_issue` are assigned to that same owner by
354
- default. Jira MCP creation forwards a concrete configured or caller-provided `assignee` to the
355
- external MCP server.
356
-
357
- Direct Jira REST configuration:
358
-
359
- ```yaml
360
- tracker:
361
- kind: jira
362
- trackers:
363
- jira:
364
- provider: jira
365
- base_url: https://example.atlassian.net
366
- email: $JIRA_EMAIL
367
- api_key: $JIRA_API_KEY
368
- project_keys: ["ENG"]
369
- # Optional provider-native scope. When present, Lorenz combines it with active_states.
370
- # jql: 'project = ENG AND labels in ("symphony")'
371
- ```
372
-
373
- Jira via an external MCP server:
374
-
375
- ```yaml
376
- tracker:
377
- kind: jira-mcp
378
- trackers:
379
- jira-mcp:
380
- provider: jira-mcp
381
- base_url: https://example.atlassian.net # optional; used for issue URLs when MCP payloads omit them
382
- project_keys: ["ENG"]
383
- mcp:
384
- url: http://127.0.0.1:5123/mcp
385
- token: $JIRA_MCP_TOKEN
386
- tools:
387
- search: atlassian_search_jira
388
- read_issue: atlassian_get_jira_issue
389
- update_status: atlassian_transition_jira_issue
390
- comment: atlassian_add_jira_comment
391
- create_issue: atlassian_create_jira_issue
392
- ```
393
-
394
- ### Local tracker (filesystem board)
395
-
396
- The local tracker runs Lorenz against a directory of Markdown files, with no Linear API key or
397
- workspace. See `WORKFLOW.local.md` for a complete example workflow.
398
-
399
- Configure it with `kind: local` and a board `path` (default `.symphony/local`):
400
-
401
- ```yaml
402
- tracker:
403
- kind: local
404
- trackers:
405
- local:
406
- provider: local
407
- path: .symphony/local
408
- id_prefix: "BOARD-" # optional, default "BOARD-"
409
- active_states:
410
- - Todo
411
- - In Progress
412
- terminal_states:
413
- - Done
414
- - Cancelled
415
- ```
416
-
417
- Both `path` and `id_prefix` are local-specific and always defaulted, so a local workflow is valid
418
- with just `kind: local`. `id_prefix` sets the issue-id prefix for the board: the tracker only treats
419
- `<prefix><n>.md` files as issues and mints new ids with it, so one board can be `BOARD-1`, `BOARD-2`
420
- and another `XXX-1`, `FEAT-1`, etc. It must be filesystem-safe (start alphanumeric, then only
421
- letters, digits, `_` or `-`); an unsafe prefix is rejected at config load. Changing the prefix of an
422
- existing board orphans files written under the old prefix (they stop matching), so set it up front.
423
-
424
- Each issue is one file named `<prefix><n>.md` (for example `.symphony/local/BOARD-7.md`, or
425
- `.symphony/local/XXX-7.md` with `id_prefix: "XXX-"`). The identifier is the file stem (`BOARD-7`).
426
- The format is YAML front matter followed by a `# Title`
427
- heading, the description, and an optional `## Comments` section:
428
-
429
- <!-- prettier-ignore -->
430
- ```markdown
431
- ---
432
- status: In Progress
433
- labels:
434
- - backend
435
- ---
436
-
437
- # Fix the retry queue
438
-
439
- The retry slot is not released when a worker fails.
440
-
441
- <!-- symphony:comments -->
442
- ## Comments
443
- - 2026-05-29T12:00:00.000Z agent: Reproduced the leak; fix in progress.
444
- ```
445
-
446
- - `status` (required) is the issue state. Active states (`Todo`, `In Progress`) mean the issue is
447
- available to work; terminal states (`Done`, `Cancelled`) mean it is finished and must not be
448
- reopened. Configure the exact sets with `active_states` / `terminal_states`.
449
- - `labels` (optional) is a YAML list. Labels feed dispatch routing the same way Linear labels do.
450
- - The `# Title` heading is the issue title; the text below it is the description.
451
- - The `## Comments` section is managed by the `local_comment` tool. The hidden
452
- `<!-- symphony:comments -->` marker delimits it so a description that itself contains a
453
- `## Comments` heading is never misparsed; treat the most recent comment block as the live
454
- workpad.
455
-
456
- Agent tools for `kind: local` (read and write, symmetric with `linear_graphql`):
457
-
458
- - `local_update_status` - move an issue to a new status (args: `issueId`, `status`).
459
- - `local_comment` - append a progress note to the issue's `## Comments` section (args: `issueId`,
460
- `body`).
461
- - `local_create_issue` - create a new board issue for out-of-scope follow-up work (args: `title`,
462
- optional `body`, optional `status`).
463
- - `local_read_issue` - read an issue's authoritative state: its current status, title, description,
464
- and comments (args: `issueId`). Use it to re-read state and recover prior progress notes on a
465
- continuation turn.
466
-
467
- Concurrent writes (multiple agents or ensemble slots) to the same board file are serialized
468
- in-process so a status change and comments are never lost. This assumes a single Lorenz daemon
469
- owns the board; editing the `BOARD-<n>.md` files from another process at the same time is out of
470
- scope.
471
-
472
- To seed a board so you can try `kind: local` immediately, use the demo seeder, which writes
473
- sample `BOARD-<n>.md` files through the same `BoardStore` the running tracker uses:
474
-
475
- ```sh
476
- npx tsx sandbox/seed-local.ts # seeds ./.symphony/local
477
- npx tsx sandbox/seed-local.ts /tmp/demo-board # seeds an explicit directory
478
- npx tsx sandbox/seed-local.ts .symphony/local 2 # seeds only the first 2 issues
479
- npx tsx sandbox/seed-local.ts /tmp/demo-board 3 XXX- # seeds XXX-1..XXX-3 (match trackers.local.id_prefix)
480
- ```
481
-
482
- Point `trackers.local.path` at the directory you seeded and run Lorenz as usual. If you set a
483
- custom `id_prefix`, pass the same prefix to the seeder so the seeded ids match what the tracker
484
- expects.
485
-
486
- ### Slack tracker (mention + thread commands)
487
-
488
- The Slack tracker treats an @-mention of a bot as an issue - in a channel message or in a thread
489
- reply (a reply mention tracks its thread, anchored at the root, with the reply as the request).
490
- The request's text is the issue title/description, threaded replies are comments, and the
491
- issue's STATUS lives in the thread: the bot posts `status: <Name>` replies and humans transition
492
- with `@bot !` command mentions; the latest event wins, and the bot mirrors the state onto its own
493
- reaction for glanceability. See `WORKFLOW.slack.md` for a complete example workflow.
494
-
495
- Set up a Slack app:
496
-
497
- 1. Create a Slack app at <https://api.slack.com/apps> (from scratch) in your workspace.
498
- 2. Under "OAuth & Permissions", add these **bot token scopes**:
499
- - `channels:history` - read messages in public channels.
500
- - `groups:history` - read messages in private channels (only if you watch private channels).
501
- - `reactions:read` - read reactions (legacy status fallback and the tracking marker).
502
- - `reactions:write` - mirror status onto the bot's own reaction and mark tracked threads.
503
- - `chat:write` - post threaded replies (comments and `status:` transitions).
504
- - `users:read` - resolve user ids to names for the `slack_user_info` tool (optional but
505
- recommended).
506
-
507
- Lorenz discovers issues by paging `conversations.history` and matching the bot's @-mention
508
- in message text, so it does not need `app_mentions:read`. Only add that scope if you separately
509
- wire up the Events API / `app_mention` subscription, which Lorenz does not use today.
510
-
511
- `conversations.history` is rate-limited (newer non-Marketplace apps can be throttled to roughly
512
- one request per minute), and each poll re-scans recent channel history. The shipped Slack
513
- workflow therefore sets a conservative `polling.interval_ms` of `60000` (one minute), and you
514
- should point it at dedicated, low-traffic channels so a busy channel does not trigger sustained
515
- `429`s. The transport's `429`/`Retry-After` backoff and per-channel `poll_error` handling cover
516
- transient limits on top of that.
517
-
518
- 3. Install the app to the workspace and copy the **Bot User OAuth Token** (starts with `xoxb-`).
519
- Export it as `SLACK_BOT_TOKEN`; Lorenz resolves it into `trackers.slack.api_key`.
520
- 4. Find the app's **bot user id** (the `U...` id, shown on the app's "App Home" / via
521
- `auth.test`). Export it as `SLACK_BOT_USER_ID` and reference it as
522
- `trackers.slack.bot_user_id`.
523
- 5. Invite the bot to each channel you want it to watch (`/invite @your-bot`). A bot only sees
524
- `*:history` for channels it has joined.
525
- 6. Collect the **channel IDs** (`C...`, from the channel's "About" panel) for those channels and
526
- list them under `trackers.slack.channels`.
527
-
528
- Configure it with `kind: slack`:
529
-
530
- ```yaml
531
- tracker:
532
- kind: slack
533
- trackers:
534
- slack:
535
- provider: slack
536
- channels:
537
- - C0123456789
538
- bot_user_id: $SLACK_BOT_USER_ID
539
- emoji_states:
540
- eyes: In Progress
541
- white_check_mark: Done
542
- x: Cancelled
543
- active_states:
544
- - Todo
545
- - In Progress
546
- terminal_states:
547
- - Done
548
- - Cancelled
549
- ```
550
-
551
- `SLACK_BOT_TOKEN` (the bot token), a non-empty `channels` list, and `trackers.slack.bot_user_id`
552
- (`SLACK_BOT_USER_ID`) are all **required**. The bot user id scopes issue creation to the bot's own
553
- mentions: only messages that mention that exact user become issues, and only that leading mention
554
- is stripped from the title. It is required so that ordinary human-to-human `<@U...>` mentions in a
555
- watched channel never spawn agents or expose their text to workers. If it is unset or resolves
556
- empty, config validation fails and the production transport fails closed (it scans nothing).
557
- Channel entries resolve `$VAR` references the same way `bot_user_id` does.
558
- `trackers.slack.assignee` is rejected for `kind: slack`: messages carry no assignee, so an
559
- assignee-partitioned deployment would otherwise silently dispatch everything everywhere.
560
-
561
- The issue identifier is the message reference in `<channel>:<ts>` form (for example
562
- `C0123456789:1717000000.000100`); that is the `issueId` passed to the write tools. Issues also
563
- carry a permalink (`{{ issue.url }}`, dashboard links) built from the workspace URL that
564
- `auth.test` reports, and `slack_read_thread` returns the same permalink for linking the source
565
- message from commits and PRs.
566
-
567
- Status is derived from the issue's thread: the bot's own `status: <Name>` replies (posted by
568
- `slack_update_status`) and human command mentions are ts-ordered events, and the latest wins.
569
- The human commands are:
570
-
571
- - `@bot !done` / `@bot !cancel` / `@bot !in progress` / `@bot !todo` - transition to the
572
- standard state.
573
- - `@bot !status <Name>` - transition to any configured active/terminal state (custom names
574
- too).
575
- - `@bot !reopen` - back to the first active state.
576
- - Any other `@bot` mention on a terminal issue re-opens it: mentioning the bot again always
577
- means "this needs attention".
578
-
579
- Reactions are per-author in Slack (the bot cannot remove a human's reaction and vice versa), so
580
- they are only the bot's visibility mirror, controlled by `emoji_states` (`:eyes:` ->
581
- `In Progress`, `:white_check_mark:` -> `Done`, `:x:` -> `Cancelled` by default). Threads that
582
- have never seen a status event fall back to the reaction-derived reading, so reaction-managed
583
- threads keep working. Two optional keys tune tracking: `marker_emoji` (default `robot_face`) is
584
- the reaction the bot drops on a reply-tracked thread's root, and `reply_lookback_days` (default
585
- `2`) bounds how far back untracked threads are inspected for new reply-mention requests.
586
-
587
- Agent tools for `kind: slack`, served by the `slack` tool pack (mounted by default alongside the
588
- provider-neutral `tracker` pack):
589
-
590
- - `slack_update_status` - set the issue's status by posting the bot's authoritative `status:`
591
- thread reply, then mirror the bot's reaction (args: `issueId`, `status` - any configured
592
- active/terminal state name).
593
- - `slack_comment` - post a threaded reply on the source message as a comment (args: `issueId`,
594
- `body`).
595
- - `slack_read_thread` - read the issue's authoritative state: thread-derived status, source
596
- message, request reply (for thread-tracked issues), reactions, permalink, and all replies
597
- (args: `issueId`). Use it to re-read state, catch new human replies/commands, and recover
598
- prior progress notes on a continuation turn.
599
- - `slack_query` - read-only query over the tracked issues in the watched channels (bot-mention
600
- roots plus bot-marked threads), with thread-derived state: filter with the shared JSON
601
- predicate DSL, project fields, order, and page; `expand` adds `thread` and `reactions` (args:
602
- `channels?`, `where?`, `select?`, `expand?`, `order_by?`, `limit?`, `offset?`).
603
- - `slack_user_info` - resolve a `U...` user id to its profile: name, real name, display name,
604
- bot flag (args: `userId`).
605
- - `slack_channel_context` - read the channel conversation around a tracked issue's source
606
- message, ascending (args: `issueId`, `before?` default 10 max 50, `after?` default 10 max 50).
607
-
608
- There is no `slack_create_issue`, and the neutral `tracker_create_issue` reports itself as
609
- unavailable on Slack: issues are created by humans @-mentioning the bot, not by the agent.
610
-
611
- Routing note: Slack issues carry only hashtag-derived labels (a `#tag` in the message text
612
- becomes the label `tag`); they are not otherwise routed or assigned. Dispatch treats a label as a
613
- route only when it starts with `route_label_prefix`, so the Slack workflow sets
614
- `route_label_prefix: route-`. Tag a message `#route-<name>` to route it: `#route-backend` becomes
615
- the label `route-backend`, which dispatch resolves to the route `backend` (set `only_routes`
616
- accordingly). Plain hashtags such as `#backend` stay non-route labels; with the default
617
- `accept_unrouted: true` all Slack mentions are still picked up.
67
+ Linear is the default tracker: issues live in a Linear project, read access uses `LINEAR_API_KEY`,
68
+ and project selection uses `project_slug`. Route labels such as `Lorenz:backend` let multiple
69
+ instances share one project. Setup and configuration are in
70
+ [Linear tracker](./docs/trackers/linear.md). Other sources (Jira, Slack, local, memory) are covered
71
+ under [Trackers](./docs/trackers/index.md).
618
72
 
619
73
  ## Workflow Prompt
620
74
 
621
- The prompt body can read these public issue and run fields:
622
-
623
- - `{{ issue.identifier }}`
624
- - `{{ issue.title }}`
625
- - `{{ issue.description }}`
626
- - `{{ issue.state }}`
627
- - `{{ issue.state_type }}`
628
- - `{{ issue.labels }}`
629
- - `{{ issue.url }}`
630
- - `{{ issue.id }}`
631
- - `{{ issue.priority }}`
632
- - `{{ issue.branch_name }}`
633
- - `{{ issue.assignee_id }}`
634
- - `{{ issue.created_at }}`
635
- - `{{ issue.updated_at }}`
636
- - `{{ issue.assigned_to_worker }}`
637
- - `{{ issue.blocked_by }}`
638
- - `{{ attempt }}`
639
- - `{{ ensemble.enabled }}`
640
- - `{{ ensemble.slot_index }}`
641
- - `{{ ensemble.size }}`
642
-
643
- Workspace tests render representative Liquid constructs: conditionals, null fallbacks, loops,
644
- `forloop` metadata, nested blocker refs, and common filters.
75
+ The prompt body reads public issue and run fields as Liquid variables, such as
76
+ `{{ issue.identifier }}`, `{{ issue.title }}`, `{{ issue.description }}`, and `{{ attempt }}`. The
77
+ complete variable list is in the [Workflow prompt reference](./docs/reference/workflow-prompt.md).
645
78
 
646
79
  ## Skills
647
80
 
648
- The `.codex/skills/` directory in this repo contains orchestration skills referenced by the example
649
- workflow files:
81
+ The `skills/` directory holds orchestration skills (`lorenz-commit`, `lorenz-push`, `lorenz-pull`,
82
+ `lorenz-land`, `lorenz-debug`) referenced by the example workflows. Lorenz copies skills into
83
+ `.lorenz/skills/` in each prepared workspace before the agent starts. See
84
+ [Skills](./docs/agents/skills.md) for how `agent.skills` and tool-pack skills are resolved.
650
85
 
651
- - `symphony-commit` produces clean, logical commits.
652
- - `symphony-push` pushes branches and creates or updates PRs.
653
- - `symphony-pull` merges the latest `origin/main` into a working branch.
654
- - `symphony-land` monitors and merges approved PRs.
655
- - `symphony-debug` investigates stuck runs and execution failures.
86
+ ## Observability
656
87
 
657
- Lorenz overlays skills into each prepared workspace before the agent starts, copying each
658
- configured directory whole into the agent's skills directory - `.codex/skills/` for Codex,
659
- `.claude/skills/` for Claude (the active executor chooses). Skills come from two places:
88
+ The terminal dashboard shows agents, throughput, runtime, token usage, rate limits, sessions, the
89
+ retry queue, and dispatch blocks. The web dashboard exposes the same runtime snapshot over a local
90
+ HTTP server, started with `--port` or `server.port`. Routes, the WebSocket stream, and `/mcp` tool
91
+ serving are documented in [Observability](./docs/observability.md).
660
92
 
661
- - **`agent.skills`** - a list of skill directories you maintain. Each entry is one skill directory
662
- (e.g. `./.codex/skills/symphony-land`) and is copied to `<skills>/<directory-name>`. Relative
663
- paths resolve from the workflow file directory.
664
- - **Tool packs** - a mounted tool pack can bundle the skill that documents it, so the skill ships
665
- automatically when the tool is in use. The Linear pack bundles `symphony-linear` (raw Linear
666
- access via the injected `linear_graphql` tool for Codex or the `/mcp` endpoint for Claude), so
667
- enabling Linear tools overlays that skill without listing it under `agent.skills`.
93
+ ## Contributing
668
94
 
669
- ## Observability
95
+ ### Requirements
670
96
 
671
- The terminal dashboard shows agents, throughput, runtime, token usage, rate limits, running
672
- sessions, retry queue, and dispatch blocks. The web dashboard exposes the same runtime snapshot
673
- through a local HTTP server.
674
-
675
- Start the web dashboard with `--port` or `server.port`:
97
+ [mise](https://mise.jdx.dev/) manages Node 24 and pnpm 9 from `mise.toml`:
676
98
 
677
99
  ```sh
678
- pnpm start -- WORKFLOW.md --port 4000
100
+ mise trust
101
+ mise install
102
+ pnpm install
679
103
  ```
680
104
 
681
- API routes:
105
+ ### Run
682
106
 
683
- - `/`
684
- - `/api/v1/state`
685
- - `/api/v1/runs`
686
- - `/api/v1/runs?id=<run-id>`
687
- - `/api/v1/refresh`
688
- - `/api/v1/:issue_identifier`
107
+ Build, then run the CLI against a workflow file:
689
108
 
690
- Live updates (ops state and trace events) stream over the `/ws` WebSocket endpoint.
109
+ ```sh
110
+ pnpm build
111
+ pnpm start -- WORKFLOW.md
112
+ pnpm start:once -- --dry-run --no-tui WORKFLOW.md
113
+ ```
691
114
 
692
- Claude sessions use `/mcp` for injected dynamic tools when the runtime has started an
693
- observability server. The server also starts automatically for Claude workflows so the ACP bridge
694
- can reach those tools.
115
+ ### Workspace Layout
695
116
 
696
- `lorenz runs` queries the same API for run history, cost summaries, retry summaries, and raw
697
- JSON output.
117
+ `apps/cli` is the composition root; `packages/*` is the provider-agnostic engine;
118
+ `extensions/*` are the tracker backends; `test/` holds workspace-level tests. The layering rules
119
+ and the recipe for adding a tracker live in [Architecture](./docs/architecture.md) and the
120
+ [Source map](./docs/source-map.md).
698
121
 
699
- ## Testing
122
+ ### Testing
700
123
 
701
124
  ```sh
702
125
  mise run tidy
@@ -704,52 +127,25 @@ mise run check
704
127
  ```
705
128
 
706
129
  `mise run tidy` formats and applies lint fixes. `mise run check` runs typecheck, build, tests, and
707
- lint.
708
-
709
- Useful direct commands:
130
+ lint. When running Vitest directly, rebuild first so tests exercise the current compiled packages.
710
131
 
711
- ```sh
712
- pnpm typecheck
713
- pnpm build
714
- pnpm lint
715
- pnpm test
716
- pnpm test:watch
717
- ```
132
+ ### Live Tests
718
133
 
719
- When running Vitest directly, rebuild first so tests exercise the current compiled packages.
720
-
721
- ## Live Tests
722
-
723
- Live tests are opt-in and launch real CLIs or services in isolated workspaces.
134
+ Live tests are opt-in and launch real CLIs or services in isolated workspaces:
724
135
 
725
136
  ```sh
726
137
  pnpm test:live:codex
727
138
  pnpm test:live:linear-codex
728
139
  pnpm test:live:claude
729
140
  pnpm test:live:ssh
730
- pnpm test:live:linear-sandbox
731
141
  ```
732
142
 
733
- `pnpm test:live` runs the Codex, Linear plus Codex, and Claude live tests.
734
-
735
- Environment knobs:
143
+ `LORENZ_LIVE_SSH_WORKER_HOSTS` is a comma-separated list of real SSH workers. When it is unset, the
144
+ SSH live test can use disposable local workers if Docker, `ssh-keygen`, and Codex auth are
145
+ available. `LINEAR_API_KEY` is required for Linear live tests, and
146
+ `LORENZ_TS_CLAUDE_ACP_BRIDGE_COMMAND` enables the Claude live tests.
736
147
 
737
- - `SYMPHONY_TS_CODEX_ACP_COMMAND` overrides the Codex ACP bridge command for live tests.
738
- - `SYMPHONY_TS_CLAUDE_ACP_BRIDGE_COMMAND` enables Claude live tests.
739
- - `SYMPHONY_TS_CLAUDE_ACP_BRIDGE_ARGS` supplies Claude ACP bridge args as a JSON string array.
740
- - `LINEAR_API_KEY` is required for Linear live tests and MCP canaries.
741
- - `LINEAR_PROJECT_SLUG` selects the Linear project for `pnpm test:live:linear-codex`.
742
- - `SYMPHONY_LIVE_SSH_WORKER_HOSTS` is a comma-separated list of real SSH workers.
743
- - When `SYMPHONY_LIVE_SSH_WORKER_HOSTS` is unset, the SSH live test can use disposable local
744
- workers if Docker, `ssh-keygen`, and Codex auth are available.
745
- - `SYMPHONY_LIVE_DOCKER_CODEX_AUTH_JSON` points disposable workers at a Codex auth file. The
746
- default is `~/.codex/auth.json`.
747
- - `CLAUDE_CODE_OAUTH_TOKEN` or `SYMPHONY_LIVE_DOCKER_CLAUDE_CODE_OAUTH_TOKEN` lets disposable
748
- workers run the remote Claude canary.
749
- - `SYMPHONY_TS_REQUIRE_REMOTE_CLAUDE=1` makes the remote Claude canary mandatory in the SSH live
750
- test.
751
-
752
- ## Packaging
148
+ ### Packaging
753
149
 
754
150
  ```sh
755
151
  pnpm build
@@ -759,16 +155,12 @@ pnpm --filter @lorenz/cli pack --dry-run
759
155
  The CLI package includes the built binary. Workspace documentation, workflow fixtures, and test
760
156
  evidence stay at the workspace root.
761
157
 
762
- ## Compatibility Contracts
763
-
764
- The checked-in workflow files are executable fixtures:
765
-
766
- - `WORKFLOW.md`
767
- - `WORKFLOW_FULL_ACCESS.md`
158
+ ### Compatibility Contracts
768
159
 
160
+ The checked-in workflow files (`WORKFLOW.md`, `WORKFLOW_FULL_ACCESS.md`) are executable fixtures.
769
161
  `pnpm test` guards workflow docs, prompt rendering, dashboard snapshots, runtime behavior, and CLI
770
162
  documentation. Update the fixture and the matching test together when the public contract changes.
771
163
 
772
164
  ## License
773
165
 
774
- This project is licensed under the [Apache License 2.0](../LICENSE).
166
+ See [CHANGELOG.md](CHANGELOG.md) for notable changes. This project is licensed under the [Apache License 2.0](LICENSE).