smol-symphony 0.1.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.
- package/AGENTS.md +73 -0
- package/DESIGN.md +453 -0
- package/LICENSE +21 -0
- package/PRODUCT.md +106 -0
- package/README.md +232 -0
- package/SPEC.md +2169 -0
- package/WORKFLOW.md +269 -0
- package/WORKFLOW.template.md +307 -0
- package/dist/agent/acp.js +304 -0
- package/dist/agent/acp.js.map +1 -0
- package/dist/agent/adapters.js +275 -0
- package/dist/agent/adapters.js.map +1 -0
- package/dist/agent/codex.js +439 -0
- package/dist/agent/codex.js.map +1 -0
- package/dist/agent/runner.js +394 -0
- package/dist/agent/runner.js.map +1 -0
- package/dist/agent/smolvm.js +174 -0
- package/dist/agent/smolvm.js.map +1 -0
- package/dist/bin/symphony.js +205 -0
- package/dist/bin/symphony.js.map +1 -0
- package/dist/http.js +1189 -0
- package/dist/http.js.map +1 -0
- package/dist/logging.js +45 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp.js +478 -0
- package/dist/mcp.js.map +1 -0
- package/dist/orchestrator.js +683 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/prompt.js +65 -0
- package/dist/prompt.js.map +1 -0
- package/dist/trackers/local.js +344 -0
- package/dist/trackers/local.js.map +1 -0
- package/dist/trackers/types.js +10 -0
- package/dist/trackers/types.js.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow.js +385 -0
- package/dist/workflow.js.map +1 -0
- package/dist/workspace.js +196 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +68 -0
- package/scripts/build-vm.sh +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# smol-symphony
|
|
2
|
+
|
|
3
|
+
A small TypeScript orchestrator that reads issues off a local Markdown tracker,
|
|
4
|
+
prepares per-issue workspaces, and runs coding agents (Claude Code, Codex,
|
|
5
|
+
OpenCode) inside isolated [smolvm](https://smolmachines.com/) microVMs over the
|
|
6
|
+
[Agent Client Protocol](https://agentclientprotocol.com).
|
|
7
|
+
|
|
8
|
+
The agent signals completion through an injected MCP server (`mark_done`,
|
|
9
|
+
`request_human_steering`); the orchestrator handles state, retry, concurrency,
|
|
10
|
+
and produces either a pull request or a `git format-patch` bundle per issue.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
14
|
+
│ symphony (node host) │
|
|
15
|
+
│ │
|
|
16
|
+
│ issues/<state>/*.md ─┐ │
|
|
17
|
+
│ WORKFLOW.md ─┼──▶ orchestrator ──▶ agent runner │
|
|
18
|
+
│ WORKFLOW.template.md │ (poll → reconcile → dispatch) │
|
|
19
|
+
│ │ │ │
|
|
20
|
+
│ │ ▼ ACP (JSON-RPC) │
|
|
21
|
+
│ │ ┌──────────────────────────┐ │
|
|
22
|
+
│ │ │ smolvm (per-issue VM) │ │
|
|
23
|
+
│ │ │ adapter binary │ │
|
|
24
|
+
│ │ │ workspace mount │ │
|
|
25
|
+
│ │ │ mcp client ←───────────┼─┼─▶ symphony MCP
|
|
26
|
+
│ │ └──────────────────────────┘ │ mark_done
|
|
27
|
+
│ ▼ │ request_human_steering
|
|
28
|
+
│ HTTP dashboard (HTMX): / │
|
|
29
|
+
│ attention · sessions · on disk · new issue · totals │
|
|
30
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
Prerequisites:
|
|
36
|
+
|
|
37
|
+
- Node.js ≥ 20.
|
|
38
|
+
- A `smolvm` binary on `$PATH` with the server reachable on the configured
|
|
39
|
+
endpoint (e.g. `smolvm serve start --listen unix:///run/user/$UID/smolvm.sock`).
|
|
40
|
+
- A packed VM image (one-time): `bash scripts/build-vm.sh` produces
|
|
41
|
+
`.vm/symphony.smolmachine.smolmachine` (~1.1 GB; ships `claude-agent-acp`,
|
|
42
|
+
`codex-acp`, and `opencode` on the guest `$PATH`).
|
|
43
|
+
- For the default `acp.adapter: claude`: a credentials file at
|
|
44
|
+
`~/.claude/.credentials.json` on the host (symphony reads and stages it; the
|
|
45
|
+
host directory is **not** bind-mounted into the VM).
|
|
46
|
+
|
|
47
|
+
Run:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install
|
|
51
|
+
npm run build
|
|
52
|
+
npx symphony WORKFLOW.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Open the dashboard at `http://127.0.0.1:8787/`. Drop issues into
|
|
56
|
+
`issues/Todo/` from the filesystem or the dashboard's `new issue` form;
|
|
57
|
+
symphony dispatches them on the next poll.
|
|
58
|
+
|
|
59
|
+
## Local Markdown tracker
|
|
60
|
+
|
|
61
|
+
Issues live as `.md` files under `tracker.root`. The parent directory is the
|
|
62
|
+
issue state.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
issues/
|
|
66
|
+
├── Todo/
|
|
67
|
+
│ ├── ABC-1.md
|
|
68
|
+
│ └── ABC-2.md
|
|
69
|
+
├── In Progress/
|
|
70
|
+
│ └── ABC-3.md
|
|
71
|
+
└── Done/
|
|
72
|
+
└── ABC-4.md
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Each file has YAML front matter and an optional body:
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
---
|
|
79
|
+
title: "Fix the login bug"
|
|
80
|
+
priority: 2
|
|
81
|
+
labels: [bug, auth]
|
|
82
|
+
blocked_by: [ABC-5]
|
|
83
|
+
---
|
|
84
|
+
Long-form description in the body.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
State comparison is case-insensitive. Moving the file between state
|
|
88
|
+
directories is the canonical state transition; the orchestrator does this
|
|
89
|
+
itself in response to `mark_done`. The agent inside the VM does **not** have
|
|
90
|
+
filesystem access to the tracker root: it signals completion through the
|
|
91
|
+
MCP server and the orchestrator does the file move.
|
|
92
|
+
|
|
93
|
+
## WORKFLOW.md
|
|
94
|
+
|
|
95
|
+
`WORKFLOW.md` is a YAML front matter block (orchestrator config) plus a
|
|
96
|
+
[Liquid](https://liquidjs.com/)-templated prompt body. The shipped file in
|
|
97
|
+
this repo is the canonical project workflow; see
|
|
98
|
+
[WORKFLOW.template.md](./WORKFLOW.template.md) for the annotated reference
|
|
99
|
+
covering every supported option, its type, default, and example.
|
|
100
|
+
|
|
101
|
+
Symphony watches the file and re-applies poll interval, concurrency, hooks,
|
|
102
|
+
prompt body, smolvm settings, etc. on change without restart. In-flight runs
|
|
103
|
+
keep the settings they started with.
|
|
104
|
+
|
|
105
|
+
## Dashboard
|
|
106
|
+
|
|
107
|
+
When `server.port` is set (or `--port <n>` is passed), a single-page HTMX
|
|
108
|
+
dashboard is served at `/`. Five live regions poll their own partials every
|
|
109
|
+
2s; idiomorph keeps unchanged DOM stable so polling doesn't twitch text.
|
|
110
|
+
|
|
111
|
+
- **header strip** — workflow file + tracker root + status badge
|
|
112
|
+
(`working` / `attention` / `idle`).
|
|
113
|
+
- **attention** — only present when something needs you. Steering requests
|
|
114
|
+
(question, original task, agent's context, reply textarea) and retry
|
|
115
|
+
queue ease open with a CSS `max-height` transition so the page doesn't
|
|
116
|
+
jump.
|
|
117
|
+
- **sessions** — running issues. Two-line rows: identifier + pill + turn +
|
|
118
|
+
tokens, then a dim last-message line.
|
|
119
|
+
- **on disk** — active-state issues not currently dispatched.
|
|
120
|
+
- **new issue** — collapsed `<details>` form. Posts JSON to
|
|
121
|
+
`POST /api/v1/issues`.
|
|
122
|
+
- **totals** — dim footer with token + runtime aggregate.
|
|
123
|
+
|
|
124
|
+
The steering reply form posts form-encoded with `HX-Request: true` and a
|
|
125
|
+
same-origin check. The endpoint also accepts `application/json` for direct
|
|
126
|
+
API clients. CSRF-relevant content types (`text/plain`,
|
|
127
|
+
`multipart/form-data`) are rejected with 415.
|
|
128
|
+
|
|
129
|
+
## MCP — how the agent talks back
|
|
130
|
+
|
|
131
|
+
Symphony injects an MCP server into each ACP session at
|
|
132
|
+
`http://<host>:<bound-port>/api/v1/issues/<id>/mcp`, gated by a per-dispatch
|
|
133
|
+
bearer token. Two tools:
|
|
134
|
+
|
|
135
|
+
- **`symphony.mark_done({ title, summary })`** — call once at end of a
|
|
136
|
+
successful run. `title` is a single-line imperative summary (≤72 chars);
|
|
137
|
+
`summary` is a one- to three-paragraph narrative. The orchestrator
|
|
138
|
+
atomically moves the issue file to the terminal state and stops
|
|
139
|
+
dispatching. The pair lands in
|
|
140
|
+
`<workspace>/.git/symphony-runtime/mark_done.md` (or
|
|
141
|
+
`<workspace>/.symphony-runtime/mark_done.md` when the workspace doesn't
|
|
142
|
+
have its own `.git/`) for the `after_run` hook to consume.
|
|
143
|
+
- **`symphony.request_human_steering({ question, context? })`** — call
|
|
144
|
+
when blocked on something only a human can answer. The turn ends
|
|
145
|
+
immediately; the human's reply arrives as the prompt for the next turn.
|
|
146
|
+
Steering-reply turns don't count against `agent.max_turns`.
|
|
147
|
+
|
|
148
|
+
In smolvm, the VM's `127.0.0.1` transparently reaches the host's
|
|
149
|
+
`127.0.0.1` (verified empirically), so the agent reaches the orchestrator
|
|
150
|
+
without any mount or special host alias.
|
|
151
|
+
|
|
152
|
+
## ACP — adapter registry
|
|
153
|
+
|
|
154
|
+
One ACP client (symphony's `agent/acp.ts`), two shipped adapter profiles.
|
|
155
|
+
Each profile encodes the binary symphony launches and the host credential
|
|
156
|
+
file it stages into the workspace before exec:
|
|
157
|
+
|
|
158
|
+
| Adapter | Binary | Host credential file |
|
|
159
|
+
| --------- | ------------------ | --------------------------------- |
|
|
160
|
+
| `claude` | `claude-agent-acp` | `~/.claude/.credentials.json` |
|
|
161
|
+
| `codex` | `codex-acp` | `~/.codex/auth.json` |
|
|
162
|
+
|
|
163
|
+
`WORKFLOW.md`:
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
acp:
|
|
167
|
+
adapter: claude
|
|
168
|
+
shell: bash
|
|
169
|
+
prompt_timeout_ms: 1800000
|
|
170
|
+
read_timeout_ms: 30000
|
|
171
|
+
stall_timeout_ms: 300000
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Selecting an adapter is enough — symphony auto-derives the launch command
|
|
175
|
+
that stages the credential into the workspace's runtime dir and copies it
|
|
176
|
+
into the adapter's expected guest path before exec. Set `command:` only to
|
|
177
|
+
override (testing a forked adapter, a non-standard binary path); doing so
|
|
178
|
+
opts out of automatic credential staging.
|
|
179
|
+
|
|
180
|
+
Credentials are **never bind-mounted from the host**. Symphony copies the
|
|
181
|
+
single credential file into a per-workspace location (under `.git/` when
|
|
182
|
+
the workspace has its own clone, else `.symphony-runtime/`) and refuses to
|
|
183
|
+
operate on workspaces inside the credential file's ancestor repo.
|
|
184
|
+
|
|
185
|
+
## After-run handoff: PR or patch
|
|
186
|
+
|
|
187
|
+
`WORKFLOW.md`'s `after_run` hook ships in two modes:
|
|
188
|
+
|
|
189
|
+
- **Pull request mode.** Triggered when `SYMPHONY_REPO=<owner>/<repo>` is
|
|
190
|
+
exported. The hook pushes the per-issue branch to GitHub and runs
|
|
191
|
+
`gh pr create --base $SYMPHONY_BASE_BRANCH ...`. Requires `gh auth status`
|
|
192
|
+
to be clean on the host. The token never enters the VM.
|
|
193
|
+
- **Patch bundle mode** (default). Writes
|
|
194
|
+
`.symphony/patches/<branch>.patch` via `git format-patch` so you can
|
|
195
|
+
review and apply with `git am`. No remote required.
|
|
196
|
+
|
|
197
|
+
The agent's `mark_done.md` provides the PR title/body or commit message; the
|
|
198
|
+
hook reads it from the workspace's runtime dir.
|
|
199
|
+
|
|
200
|
+
See [AGENTS.md](./AGENTS.md) for the env-var contract and switch-over
|
|
201
|
+
commands.
|
|
202
|
+
|
|
203
|
+
## Trust posture
|
|
204
|
+
|
|
205
|
+
Sandbox isolation comes from running each agent inside a smolvm microVM.
|
|
206
|
+
The VM has no network credentials (only the agent's API key is forwarded
|
|
207
|
+
via `smolvm.forward_env`), no tracker filesystem access (the tracker is
|
|
208
|
+
reached only through the MCP server), and stripped git remotes (set by
|
|
209
|
+
`after_create`).
|
|
210
|
+
|
|
211
|
+
Within the ACP session, the orchestrator follows SPEC §10.5's "high-trust"
|
|
212
|
+
posture:
|
|
213
|
+
|
|
214
|
+
- Command execution and file change approvals: auto-approve.
|
|
215
|
+
- User-input-required turns: end the turn (the orchestrator retries on
|
|
216
|
+
backoff).
|
|
217
|
+
- Unsupported dynamic tool calls: return failure; the session keeps running.
|
|
218
|
+
|
|
219
|
+
## Tests
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
npm run typecheck # tsc --noEmit
|
|
223
|
+
npm test # 67 tests across workflow, tracker, prompt, workspace,
|
|
224
|
+
# adapters, http, and mcp surfaces
|
|
225
|
+
npm run build # tsc emit to dist/
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
An end-to-end smoke run needs a real smolvm + VM image.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT. See [LICENSE](./LICENSE).
|