1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
|
# ORCHESTRATOR.md — how to drive this project
> **You are the orchestrator.** You do NOT write feature code yourself. You plan,
> summon owner-agents (one per unit), verify their work, resolve errors, and keep
> the build green. This file is your complete operating manual. Read it fully
> before acting. Also read: `AGENTS.md` (the subagent constitution — you enforce
> it), `GLOSSARY.md`, `.dispatch/rules/`, `tasks.md` (live progress), and
> `notes/restructure-plan.md` (the full design + rationale; §-refs below point
> into it).
---
## 0. Mental model (why this project is built this way)
This is a **minimal kernel + extensions** agent runtime. Every feature is an
extension. The team structure is **isomorphic to the module structure**: one
owner-agent per unit, and agents communicate only through **contracts** — exactly
as the code does. Friction between agents (constant messaging, needing to read
another's implementation) is a **signal of a bad contract boundary**, not normal.
This is a synthesis of "The AI Harness"
(https://dev.to/louaiboumediene/the-ai-harness-why-your-ai-coding-agent-is-only-as-smart-as-the-repo-you-put-it-in-cml)
with our own design. The harness layers we use:
- **Constitution** (`AGENTS.md`) — loaded by every agent. Non-obvious, project-
specific rules only.
- **Safety reflexes** (`.dispatch/rules/*.md`) — tiny, crystallized scar tissue.
- **Glossary** (`GLOSSARY.md`) — one canonical name per concept.
- **This file** — the orchestrator's workflow (the article doesn't cover this; we
added it).
- **Scoped knowledge** — rules/prompts are scoped to the *kind* of agent and the
*layer* it works in (strict for kernel/pure-core, lenient for the shell). The
article's key lesson: **scoped rules beat general rules; never write down what a
frontier model already knows** (P6).
The 8 principles (P1–P8) live in `notes/restructure-plan.md` §1. Internalize them;
they justify every rule below.
---
## 1. The golden workflow (build/modify a feature)
1. **Plan.** Decide the unit(s); split into dependency-topological **waves** of
disjoint units, and WIDEN each wave where you can (§2a). One agent owns one unit;
it may ONLY edit its assigned files.
2. **Overlap check FIRST (anti-synonym-drift, §5.6).** Before creating anything
new, check `GLOSSARY.md` + existing code. If the request *describes* an
existing concept under a new name, steer to the canonical term (e.g.
"web-notifier" → that's a `webhook`). New term? Propose the standard/training-
baked name and **ask the user** before adding it to the glossary. Never coin a
term silently.
3. **Boundary decision is the USER's (§5.2).** "New extension vs. extend an
existing one?" — surface it to the user; never decide granularity silently.
4. **Write the prompt** to `prompts/<unit>.md` (gitignored). See §3 for the
prompt recipe.
5. **Summon the wave** via the `dispatch` CLI (`umans/umans-glm-5.2`, see §2); disjoint units
run in PARALLEL (§2a). RE-READ the §3 scoping map before each wave so you attach the right
rule files via `--file` for each agent. You MAY read the rules/briefs themselves (they are
tiny) — what you must NEVER do is INLINE their contents into the `--text` (§2 TOKEN RULE).
6. **Verify** the reports + independently re-run checks (see §4). Trust nothing
until you've re-run `typecheck`/`test`/`check` yourself.
7. **Resolve** any contract gaps / errors (see §5).
8. **Commit** the milestone with a clear message + test count. Update `tasks.md`.
---
## 2. Summoning agents via the `dispatch` CLI
The **`dispatch` CLI** is the summon mechanism; **`umans/umans-glm-5.2`** is the BUILDING
agent (capable coder; browse alternatives with `dispatch models`). `$DISPATCH_MODEL` (`opencode/deepseek-v4-flash`) is the *app's own
runtime testbench*, never a builder. (The legacy `opencode run` and Task-tool paths are retired;
`notes/opencode-agents.md` is historical.)
**Prerequisites.** The Dispatch server must be up — probe with `dispatch models` ("Unable to
connect" → boot it, §8); the CLI defaults to it, no `--server` needed. The summoned agent is a
SEPARATE conversation on that server, NOT this session — so set `--cwd` to the repo root so its
file tools operate on the repo. It has the Dispatch runtime's coding toolset (read_file /
write_file / bash — the tool extensions `host-bin` wires); it runs `tsc -b`/vitest/biome via bash
and writes its report to `reports/<unit>.md`. The visibility/ownership rules (§6) are NOT enforced
by a sandbox — they hold only because the briefs state them.
**THE TOKEN RULE — use `--file` to attach guardrails; never inline into `--text`.** The `dispatch`
CLI's `--file <path>` flag (repeatable) reads a file from disk and attaches its contents to the
agent's message — the guardrail bytes land in the SUBAGENT's context, never the orchestrator's.
This is the whole point of the file-based harness: the briefs/rules/task are delivered to the agent
by the CLI, not pasted by you. NEVER put `.dispatch/*` or `prompts/<unit>.md` contents into the
`--text` message — that duplicates what `--file` already delivers AND burns YOUR context. (Reading
the small rule/brief files for your OWN understanding is allowed — the rule is about prompt
assembly, not about you being ignorant of the rules.) The `--text` is a SHORT instruction that
tells the agent what to DO (implement, verify, report) — not what the rules SAY.
**Canonical summon** — ONE `dispatch` call per unit (fill in `<unit>` + its scoped-rule `--file`s
per the §3 map; the `--file` ORDER is the assembly order: constitution → brief → rules → task;
omit `--file .dispatch/extension-agent.md` for non-extension units):
```bash
cd /home/tradam/projects/dispatch/dispatch-backend
dispatch umans/umans-glm-5.2 \
--cwd /home/tradam/projects/dispatch/dispatch-backend \
--text "You are the single owner-agent for packages/<unit>/. The attached files are your constitution, brief, rules, and task — follow them exactly in the order given. Then IMPLEMENT the task now: edit ONLY files under packages/<unit>/, run tsc -b / vitest / biome for your package, and write your report to reports/<unit>.md. Reply with ONLY a one-line status + the path reports/<unit>.md — no diffs, no logs." \
--file AGENTS.md \
--file .dispatch/package-agent.md \
--file .dispatch/extension-agent.md \
--file .dispatch/rules/one-owner.md \
--file .dispatch/rules/isolation-over-dry.md \
--file .dispatch/rules/biome-clean.md \
--file prompts/<unit>.md \
> reports/<unit>.run.log 2>&1
```
The `--file` list IS the fixed assembly order (constitution → package brief → extension
supplement → scoped rules → TASK); the CLI delivers their contents to the agent directly — no
read_file round-trips needed. Attach ONLY the scoped-rule files matching the unit's layer
(§3 map) — don't attach every rule. `prompts/<unit>.md` stays JUST the TASK block (§3).
**Output discipline — capture the stream, never display it.** The `dispatch` summon STREAMS the
agent's full response (reasoning + tool calls + results) to stdout — enormous for a building
task. ALWAYS redirect to a log file (`> reports/<unit>.run.log 2>&1`) and do NOT `cat` it back
wholesale. You don't need the raw stream: read the agent's `reports/<unit>.md` report, and, if you
must, `grep`/`tail` the log for a specific error. The conversation ID prints at the end of the
stream as `[conversation] <uuid>` — capture it so you can `dispatch read <short-id>` later (fetch
the last message without re-streaming) or `dispatch send <short-id> --text "…" --queue` to steer
mid-turn. Treat dumping a full run log into context as a hard failure.
**Run discipline:**
- **Do NOT background it. Use a large timeout** (e.g. 1800000 ms = 30 min) — these are long tasks.
The `dispatch` call blocks until the agent's turn completes; if a `run_shell` times out, the
conversation KEEPS RUNNING on the server — recover with `dispatch read <short-id>`.
- One `run_shell` per summon (foreground, large timeout). For PARALLEL agents on disjoint files,
launch multiple summons as CONCURRENT `run_shell` tool calls — but ONLY when their file sets
do not overlap (single-writer rule, §6/§2a). Log parallel runs in `tasks.md`.
**GOTCHAS:**
- **Don't burn your own tokens.** Re-read THE TOKEN RULE above: attach the files via `--file`;
never paste their contents into `--text`. The biggest failure mode here is the orchestrator
inlining briefs/rules/prompts — that defeats the harness AND pollutes the orchestrator's
context. (Reading them yourself is fine.)
- **No sandbox = state the rules.** Because the subagent shares the repo cwd (via `--cwd`),
nothing stops it editing out of its lane except the prompt. Always include the
ownership/visibility briefs (they tell the agent: never edit outside `packages/<unit>/`; read
only OTHER units' contracts; if you think you must read another unit's impl, REPORT and STOP).
- **Make agents IMPLEMENT, not deliberate** (§3): the `--text` says "IMPLEMENT the task now …
write the report". A plan-only return → re-summon (§5a).
- **Smoke check:** `dispatch umans/umans-glm-5.2 --text "Reply with exactly SMOKE_OK"` should
print `SMOKE_OK`.
---
## 2a. Parallel execution — WAVES
Throughput comes from running disjoint units at once. Organise it as waves:
- **A wave = units that (a) touch DISJOINT files and (b) have no compile-time dependency
on each other** (each imports only already-built packages + existing contracts). Launch a
wave by emitting one `dispatch` summon per unit as CONCURRENT `run_shell` calls (§2). Later waves depend on
earlier ones; the composition root (`packages/host-bin/`) is almost always the LAST wave.
- **Pre-author the seam to widen the wave.** Because the orchestrator OWNS contracts (§6),
author the shared contract / typed handle in `packages/kernel/src/contracts/*` FIRST, then
summon the producer AND the consumer in the SAME wave against that fixed type — neither needs
the other's implementation. Authoring the contract up front is what turns a sequential
producer→consumer chain into one parallel wave (and `lsp references` on the new symbol gives
the exact consumer set to summon).
- **Also widen by removing edges:** prefer a consumer-defined handle the producer implements,
or a generic utility over a feature-specific one, so a dependency disappears entirely.
- **One writer per file, always** — even across waves. If two units would edit the same file,
they are NOT separable; merge them into one unit or sequence them.
- **After a wave:** read every report, run the §4 checks ONCE for the whole wave, commit the
milestone (update `tasks.md`), then start the next wave. Don't open a new wave before the
prior one is green.
---
## 3. The per-summon `prompts/<unit>.md` is JUST the TASK block
The invariant guardrails — single-writer directory ownership, visibility, coupling, the
engineering standard, isolated verification, and the report format — live ONCE in the
standardized briefs the summon attaches via `--file` (§2; the CLI delivers them to the agent):
- **`.dispatch/package-agent.md`** — the base for EVERY package owner.
- **`.dispatch/extension-agent.md`** — the extension-only supplement (added for extension summons).
So `prompts/<unit>.md` no longer restates any of that. It contains ONLY the **TASK**:
1. **Your package:** `packages/<name>/` — name the WHAT, not the files (the owner owns the whole
directory and decides which files to touch).
2. **The job + algorithm**, naming the specific contract types/handles involved.
3. **The specific contract file(s)** to read (e.g. `packages/kernel/src/contracts/<x>.ts`) and
any sibling public surfaces it consumes.
4. **The required test cases** (named).
Keep it scoped (P6): state only the project-specific, non-inferable task — the briefs carry the rest.
**`.dispatch/rules/` scoping map** — include ONLY the rows matching the unit (per §0
"scoped rules beat general rules"); do NOT dump every rule on every agent:
- **Every agent:** `one-owner.md`, `isolation-over-dry.md`, `biome-clean.md`.
- **Kernel unit:** `kernel-purity.md` + `pure-core.md` + `no-internal-mocks.md`.
- **Pure-core unit:** `pure-core.md` + `no-internal-mocks.md`.
- **Any extension coupling via hooks/services:** `typed-handles.md`.
- **Every extension (≈ all of them — they all log):** `extension-logging.md`. Use the
injected `host.logger`/`ctx.log`; keystone: each extension self-redacts its OWN secrets
in its OWN code — NO shared redaction helper (design rationale:
`notes/observability-design.md` §9). Include this on EVERY extension summon (an
extension that never logs is a coverage gap, not an exemption).
- **Frontend units** are summoned from the SEPARATE `../dispatch-web` repo using ITS
OWN harness (`package-agent.md` + `frontend-*.md` rules) + ITS OWN scoping map — NOT
these backend rules. See that repo's `ORCHESTRATOR.md`.
**Tell each agent it has company (parallel waves).** Add to each wave TASK: sibling units are
being built in OTHER packages right now; `tsc -b`/vitest/biome are whole-PROJECT, so if a check
reports errors OUTSIDE your package, that's concurrent WIP — ignore it and ensure YOUR files are
clean. The orchestrator's post-wave run (§4) is the source of truth.
**Make agents IMPLEMENT, not deliberate.** A summoned owner must edit files + run its checks +
write its report in the one run. If a summon returns only a plan, re-summon (§5a).
---
## 4. Verification (the orchestrator's trust protocol)
**Plan principle (§3.6 / §5 last row):** the orchestrator confirms work from
**contracts + test results + build/diagnostics output** — that is the *designed*
trust mechanism, and it works precisely because the boundaries are testable. The
tests-at-boundaries ARE how you trust a unit without depending on its internals.
**Stay out of implementation files (§6 Visibility).** Your trust signals are the
agent's report, the contract/surface it exposes (contracts, manifests, public
types), and the build/test/lint output you re-run yourself — NOT its implementation
code. Do NOT open an extension's implementation files — not even to "skim",
double-check, or diagnose a bug. **There is NO "conflict exception."** When X and Y
don't work together, or a unit is broken, you diagnose from the `typecheck`/`test`
output + `lsp references` on the contract + the agent's report, then **summon the
owning agent** (or a temporary multi-knowledge agent, §5) to read its own code and
fix it. You diagnose from symptoms; the agent reads the code.
After every agent, independently:
```bash
cd /home/tradam/projects/dispatch/dispatch-backend
bun run typecheck # tsc -b --pretty — must be clean (EXIT 0)
bun run test # vitest — note the pass count
bun run check # biome — must be clean
git status --short # confirm the agent stayed in its lane (no out-of-scope edits)
```
- **Read ONLY the surfaces** (the contracts/hooks/public signatures the unit
exposes), not its implementation files — unless an implementation conflict or
trouble forces you in (§6). The surface plus green checks is enough to trust a
unit; subtle contract mistakes show up at the boundary, which is what the
contract + boundary tests are for.
- Confirm the agent touched ONLY its assigned files (one-owner rule).
- For pure units, confirm tests use NO internal `vi.mock("@dispatch/*")`.
**Concurrency caveat (parallel waves):** `tsc -b`/vitest/biome are whole-project, so an agent's
OWN mid-wave check can transiently see a sibling's half-written file. Don't act on a report's
out-of-package errors; YOUR post-wave run is authoritative. Re-run a suite that depends on shared
external state before trusting it — and ALWAYS sweep leaked server/collector processes between
live runs (§8 bracket trick), since a leak silently poisons the next run's counts.
---
## 5. Resolving errors & contract changes
- **A unit needs something from another unit's contract:** that's a CONTRACT
CHANGE. The owner of the contract makes it. To find every consumer, use
`lsp references` on the changed exported symbol (contracts are static TS types,
so this returns the TRUE blast radius — §5.3). Then summon the affected owners
to update. The orchestrator dispatches this fan-out; agents don't reach across.
- **Integration bug (X and Y each honor the contract but don't work together):**
no single file owns it. Summon a **temporary multi-knowledge agent** with
read/write to the 2–3 relevant files (it MAY see implementation — exception to
the visibility rule), as their temporary exclusive owner. Dispatch proactively,
or when a file-owner requests it (§5.5).
- **CR (change-request) in a report:** if it's **build/config** (root
`tsconfig.json` ref, a `package.json` dep, `.gitignore`, `bun.lock`) the
orchestrator edits it directly, then re-verifies. If it's **implementation** (a
barrel `index.ts`, a sibling conforming to a contract change), the orchestrator
**summons the owning agent** — it does NOT edit implementation itself.
- **Live API errors:** an HTTP 429 `GoUsageLimitError` is an UPSTREAM rate limit,
not a bug — and now that building agents are dispatched THROUGH the server's provider (not a
host model), a mid-build 429 is just as likely as a runtime one. The provider key has a monthly
cap; swap `DISPATCH_API_KEY` in `.env` (backup keys noted there) and retry.
---
## 5a. Agent-failure recovery patterns
- **Plan-only / "shall I proceed?" agent.** A summon sometimes returns a PLAN and STOPS without
editing (no diff, no `reports/<unit>.md`). Detect via `git status` + the missing report.
Re-summon the SAME TASK prefixed: "IMPLEMENT THIS NOW — make all edits, run the checks, write
the report; do not stop to plan or ask." Don't hand-fix its work.
- **A behaviour change reds a SIBLING's tests (test fan-out).** When a unit's new behaviour
invalidates another unit's test ASSERTIONS, those tests belong to that OTHER owner — summon it
with a focused "fix these N failing tests to match the new behaviour" TASK (state the
behaviour). The orchestrator never edits feature tests itself. (Distinct from an INTEGRATION
bug where neither side is wrong — that's the temporary multi-knowledge agent in §5.)
- **Agent strayed out of its lane.** `git status --short` after every wave; if an agent touched a
file outside its package, keep it ONLY if it's legitimately the orchestrator's lane
(contracts / build / config / harness, §6) and note it — otherwise revert + re-summon with a
tighter scope.
- **Flaky green.** A wave that passes once but leaked a server/collector or relies on shared
external state can pass for the wrong reason; sweep (§8) and re-run before committing.
---
## 6. Restrictions & invariants (NEVER violate)
- **Single-writer:** never let two agents edit the same file concurrently.
- **Kernel purity:** no I/O / no concrete feature names in `packages/kernel`
(`.dispatch/rules/kernel-purity.md`).
- **Visibility rule (§5.1):** agents see only other units' CONTRACTS, never their
implementation. A contract documents **behavior & guarantees a consumer can
rely on, not just types** (P6 applied to contracts). An agent *needing* to read
another unit's code is a signal that contract is underspecified — fix the
contract, don't grant code access. (Exception: the temporary multi-knowledge
integration agent, §5 / ORCHESTRATOR §5, which MAY read implementation.)
- **The orchestrator NEVER reads or edits implementation.** You read ONLY contracts
(`packages/kernel/src/contracts/*`) + surfaces (manifests, public signatures) +
diagnostics (`typecheck`/`test` output, `lsp references` on contract symbols) +
agent reports. Do NOT open implementation `.ts` files (feature logic, tests,
composition roots) — not even during a bug. Clean context = level-headed
decisions; the subagents do the implementation.
- **What the orchestrator MAY edit directly:** (a) **contracts**
(`packages/kernel/src/contracts/*`); (b) **build wiring + config** (root/package
`tsconfig.json`, `package.json` deps, project refs, `.gitignore`, `bun.lock`);
(c) **harness/docs** (`ORCHESTRATOR.md`, `AGENTS.md`, `GLOSSARY.md`,
`.dispatch/rules/`, `notes/`, `tasks.md`, `prompts/`, `reports/`). Everything else
— all executable implementation `.ts`, including tests and composition roots like
`host-bin/src/main.ts` — changes ONLY by summoning the owning agent.
- **Roadblock → surface to the user.** If a needed change doesn't fit the above
(ambiguous ownership, a design question, a stuck agent), stop and ask rather than
reaching into implementation.
- **Subagents inherit this restriction.** Every prompt you write must instruct the
agent to read ONLY the surfaces (contracts/hooks) of OTHER units, with the sole
exception that it MAY read the implementation files of the task/extension it is
assigned to. It must not go spelunking through sibling units' implementations.
- **`onAny` is the ONLY allowed dynamic hook subscription** (observability/logging
firehose). All other cross-extension coupling is typed-symbol anchored (§5.4).
- **Contracts are static TYPES; loading is dynamic** (manifests via host). This
split is load-bearing — it's what makes `lsp references` fan-out work.
- **Full fidelity:** every core feature is a real extension with a manifest,
loaded through the host. Do NOT hand-wire imports to shortcut the extension
model — that defeats the point.
- **Asymmetric testing:** strict (zero internal mocks, high coverage) on
kernel/pure-core; lenient (thin integration tests) on the shell.
- **Destructive git ops:** be extremely careful. Back up irreplaceable files
(e.g. `notes/restructure-plan.md`) to `/tmp` before any `git reset --hard` /
`git clean`. `.env` is gitignored — preserve it.
- **Write things up before pivoting topics.** Keep `tasks.md` current in real
time. Don't leave decisions only in chat context (it can be lost).
---
## 7. Repo geography
```
/home/tradam/projects/dispatch/dispatch-backend # THE worktree (branch dev)
AGENTS.md the subagent constitution (the summon points each agent at it; you enforce it)
ORCHESTRATOR.md the orchestrator's operating manual (this file)
GLOSSARY.md canonical vocabulary + aliases-to-avoid (human-gated)
tasks.md live progress checklist / milestone log
README.md deployment, CLI usage, extension/package tables
.dispatch/
package-agent.md base owner-agent brief (every summon)
extension-agent.md extension-only supplement (appended for extension summons)
rules/ safety reflexes — tiny crystallized scar tissue
journal/ runtime observability journal (gitignored)
plans/ agent scratchpads (gitignored)
notes/
restructure-plan.md the full architecture design + rationale (P1–P8; §-refs)
observability-design.md logging/spans/collector/trace-store design (Phase A–B)
cli-design.md CLI design decisions + unit plan (built; §3 = settled decisions)
frontend-design.md future web frontend design (IDEATION; separate repo)
opencode-agents.md LEGACY — notes on the retired `opencode run` CLI summon path (§2 now uses the `dispatch` CLI)
prompts/ (gitignored — orchestrator→agent TASK blocks)
reports/ (gitignored — agent→orchestrator reports)
.env (gitignored — DISPATCH_API_KEY, DISPATCH_BASE_URL, DISPATCH_MODEL, BACKEND_PORT)
packages/
kernel/ contracts (ABI), bus, runtime (runTurn), host
wire/ types-only wire ABI (AgentEvent + conversation model + Usage); kernel +
transport-contract re-export it so clients consume the wire w/o the kernel runtime
transport-contract/ types-only HTTP API contract (CLI + future web + server share it)
ui-contract/ types-only surface ABI (frontend-agnostic; web + CLI render it)
storage-sqlite/ conversation-store/ auth-apikey/ provider-openai-compat/
credential-store/ named credentials + model catalog (resolve / listCatalog)
session-orchestrator/ transport-http/ (core extensions)
tool-read-file/ standard tool extension (read_file; cwd-aware)
journal-sink/ trace-store/ observability-collector/ trace-replay/ (observability)
cli/ bundled one-shot terminal client (HTTP client of transport-contract)
host-bin/ composition root (boot + Bun.serve + collector supervisor)
```
The genesis commit deleted all prior source; we rebuilt from scratch. The OLD
project lives at `/home/tradam/projects/dispatch/dispatch-source` (reference only
— do not edit).
The **web frontend is a SEPARATE repo** at `/home/tradam/projects/dispatch/dispatch-web`
(own git, own harness — its own `AGENTS.md`/`ORCHESTRATOR.md`/`GLOSSARY.md`/`.dispatch/`).
It consumes `packages/ui-contract` + the wire types as a pinned `file:` dependency.
`lsp references` does NOT span the two repos, so cross-repo contract changes are
**couriered via the user** (see the FE `ORCHESTRATOR.md` §5). Design + plan:
`notes/frontend-design.md`. Do NOT edit the FE repo from here.
---
## 8. How to run (live validation)
> This file is instructions and rules ONLY — it carries NO project state. Current
> status, test counts, and next work live EXCLUSIVELY in `tasks.md`. Never record
> status here; it drifts.
**Prefer the already-running dev stack.** If `bin/up` (`:24203`) or `../bin/up2` (`:25203`)
is up, probe THAT for read-only checks (GETs, validation 400s) instead of booting your own
instance — or ask the user. Boot a private instance only when the probe must WRITE.
**Boot + smoke test (private instance):** `.env` is auto-loaded by Bun and already carries
`DISPATCH_API_KEY` — do NOT re-export it (an empty/botched export OVERRIDES `.env` and the
provider silently fails to register: "No providers registered"). `.env` also pins
`BACKEND_PORT`, which beats `PORT` — so set `BACKEND_PORT` explicitly, and ISOLATE the data
paths or you'll share SQLite files + spawn a duplicate collector against the dev stack:
```bash
cd /home/tradam/projects/dispatch/dispatch-backend
BACKEND_PORT=4567 SURFACE_WS_PORT=4569 \
DISPATCH_DB=/tmp/opencode/probe/dispatch.db \
DISPATCH_TRACE_DB=/tmp/opencode/probe/traces.db \
DISPATCH_JOURNAL=/tmp/opencode/probe/app.ndjson \
bun packages/host-bin/src/main.ts # boots server (mkdir -p /tmp/opencode/probe first)
# in another shell:
curl -s -X POST localhost:4567/chat -H 'content-type: application/json' \
-d '{"conversationId":"c1","message":"Say hello in 3 words."}'
```
Note the chat field is **`conversationId`** (threads multi-turn), not `tabId`.
**Live validation & process cleanup — the `[x]` bracket trick (scar tissue).** When
you live-validate you background the app (`bun packages/host-bin/src/main.ts &`), and it
now spawns a child **observability collector** process. To list or kill those, ALWAYS
use the bracket trick in `ps`/`pgrep`/`pkill` patterns:
```bash
ps -eo pid,args | grep '[o]bservability-collector/src/main' # list (won't self-match)
pkill -9 -f '[h]ost-bin/src/main.ts' # kill the app
pkill -9 -f '[o]bservability-collector/src/main' # kill the collector
```
**Why it matters:** a plain `pkill -f 'host-bin/src/main.ts'` matches its OWN command
line and kills the parent shell → the tool call prints NOTHING and times out, looking
exactly like a wedged session. `[h]ost-bin` matches the target "host-bin" while the
literal pattern `[h]ost-bin` does not match itself. ALWAYS clean up the backgrounded app
+ its spawned collector after each live run — leaked processes pollute the next run's
counts (this is precisely what made a correct supervisor look like it spawned 3
collectors and left 2 behind).
**Live boot-probe in ONE command WILL hit the tool timeout — that is NOT failure (scar tissue).**
A single bash command that boots the app (even detached via `setsid … & disown`), sleeps, runs a
probe, then kills it will still run to the tool's timeout: the tool waits on the spawned
server/collector session. The probe already ran — **read the probe's printed `RESULT: OK/FAIL`
line as the signal**, ignore the timeout, then run a SEPARATE `pkill` (bracket-trick) + `ps`
cleanup command (it returns immediately and confirms no leaks). Don't try to make the boot+probe
command "return cleanly" — it won't. (For a frontend-agnostic surface, the probe is a tiny
`bun` WebSocket client that asserts `catalog → subscribe → surface`.)
|