summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-05 00:30:34 +0900
committerAdam Malczewski <[email protected]>2026-06-05 00:30:34 +0900
commitefb4737fd22572f757e7a9eb1034dd96ae1b6593 (patch)
treee3d630b834d199f4712caa02399bf654bfb039a6
parent9aadc668c0bc515bce9f28ff28376d990f9425f5 (diff)
downloaddispatch-efb4737fd22572f757e7a9eb1034dd96ae1b6593.tar.gz
dispatch-efb4737fd22572f757e7a9eb1034dd96ae1b6593.zip
chore(cleanup): align glossary (conversation/conversationId + drift note), ORCHESTRATOR↔plan §5/§3.6, add HANDOFF.md, host-bin reads BACKEND_PORT (24203)
-rw-r--r--GLOSSARY.md13
-rw-r--r--HANDOFF.md109
-rw-r--r--ORCHESTRATOR.md22
-rw-r--r--packages/host-bin/src/main.ts3
4 files changed, 145 insertions, 2 deletions
diff --git a/GLOSSARY.md b/GLOSSARY.md
index 4d70e0e..8ead590 100644
--- a/GLOSSARY.md
+++ b/GLOSSARY.md
@@ -13,6 +13,8 @@
| **contract** | An extension's typed, exported surface: what it exposes + what it requires. The ONLY thing other units see. | interface (when meaning the whole surface), API |
| **manifest** | An extension's declaration: id, version, apiVersion, dependsOn, contributions, capabilities, trust. | — |
| **Host API** | The object an extension receives in `activate(host)`. | host context |
+| **conversation** | A single thread of turns with its own persisted history, identified by a `conversationId`. The backend unit of continuity. (The frontend "tab" concept is out of scope for the backend rewrite.) | tab, session, thread, chat |
+| **conversationId** | The string identifier for a conversation. Threads multi-turn history; the `/chat` request field that continues an existing conversation. | tabId, sessionId, chatId |
| **turn** | One user message → assistant response cycle (may span multiple steps). | — |
| **step** | One LLM round-trip within a turn (may emit multiple tool calls). | iteration |
| **tool call** | A model's request to run a tool within a step. | function call (when meaning a tool call) |
@@ -25,3 +27,14 @@
| **session-orchestrator** | The core extension that drives a turn: load history → resolve provider/tools → call `runTurn` → persist. | — |
| **conversation-store** | The core extension persisting the append-only turn/chunk log. | message store |
| **provider** | An extension wrapping an LLM backend (`stream(messages, tools)`), provider-agnostic to the kernel. | — |
+| **AgentEvent** | An outward event the runtime emits during a turn (text-delta, tool-call, usage, done, etc.). Carries `conversationId` + `turnId`. | — |
+
+## Known vocabulary drift (tracked, not yet fixed)
+
+- **`tabId` in `AgentEvent`s and `runTurn` input.** The events contract
+ (`packages/kernel/src/contracts/events.ts`) and `RunTurnInput` still use
+ **`tabId`** where the canonical term is now **`conversationId`**. The
+ session-orchestrator maps `conversationId → tabId` as a bridge. This is a
+ CONTRACT change (kernel + every consumer) — to be done as a scoped fan-out via
+ `lsp references` (see HANDOFF.md), not a silent edit. Until then, `tabId` in
+ emitted events == the `conversationId`.
diff --git a/HANDOFF.md b/HANDOFF.md
new file mode 100644
index 0000000..5ab50c7
--- /dev/null
+++ b/HANDOFF.md
@@ -0,0 +1,109 @@
+# HANDOFF — next steps for the incoming orchestrator
+
+> Read `ORCHESTRATOR.md` first (your operating manual), then `tasks.md` (live
+> status), then this file (what to do next). The MVP is DONE and verified live;
+> this is the post-MVP backlog, ordered.
+
+## Where things stand (one paragraph)
+Kernel (contracts, bus, runtime/`runTurn`, host) + 6 core extensions
+(storage-sqlite, conversation-store, auth-apikey, provider-openai-compat,
+session-orchestrator, transport-http) + host-bin are built, full-fidelity
+(every core feature is a real manifest-loaded extension). **178 tests pass;
+typecheck + biome clean.** Multi-turn `curl` against OpenCode Go flash works
+(use the `conversationId` field). Keys are rotated to **opencode-1** (active);
+ports are **24203 backend / 24204 frontend** (in `.env`).
+
+## How to boot & smoke-test
+```bash
+cd /home/tradam/projects/dispatch/arch-rewrite
+set -a; source .env; set +a # loads DISPATCH_API_KEY + BACKEND_PORT
+bun packages/host-bin/src/main.ts # boots on BACKEND_PORT (24203)
+# another shell:
+curl -s -X POST localhost:24203/chat -H 'content-type: application/json' \
+ -d '{"conversationId":"c1","message":"Say hello in 3 words."}'
+# multi-turn: send a 2nd POST with the SAME conversationId; it sees turn 1.
+```
+A 429 `GoUsageLimitError` = upstream monthly cap, not a bug. opencode-1 is active;
+opencode-2 (in `.env` as `DISPATCH_API_KEY_OPENCODE2`) is rate-limited until reset.
+
+---
+
+## Next steps (ordered; each is one summon unless noted)
+
+### 1. Wire auth → provider properly ⟵ DO FIRST (correctness, small)
+**Problem:** `auth-apikey` is currently **vestigial**. `provider-openai-compat`
+reads its credentials straight from `host.config` (`provider.openai-compat.*`)
+and never calls `AuthContract.resolve()`. So the auth extension exists but does
+nothing — the architecture isn't actually exercising the auth seam.
+**Goal:** the provider obtains credentials via the `AuthContract` (resolved by
+the orchestrator/host-bin and handed to the provider), not by reading config
+directly.
+**Likely a 2-unit change (contract-touching → coordinate):**
+- `provider-openai-compat`: accept resolved `Credentials` (apiKey/baseURL) at
+ registration/stream instead of reading `host.config`.
+- `host-bin` (composition root): resolve `auth-apikey`'s `AuthContract` →
+ feed the provider. (Orchestrator may do this small wiring edit directly.)
+- Watch the **`ProviderContract.stream` credentials gap** noted since the
+ contracts were written — decide whether creds flow via `ProviderStreamOptions`,
+ a provider factory arg, or a resolve-at-activate step. If `provider.ts`
+ contract must change, run `lsp references` and fan out (§5.3).
+**Verify:** boot + curl still returns a real response; auth-apikey now on the
+path. Add/adjust tests so the seam is covered without internal mocks.
+
+### 2. First TOOL extension — exercise the dispatch loop ⟵ proves §3.3
+**Problem:** every turn so far runs with `tools: []`. The kernel's tool-dispatch
+loop (eager / semaphore / dedup / concurrencySafe / abort) has unit tests but has
+NEVER run end-to-end with a real tool + a real model.
+**Goal:** add a `read_file` tool extension (standard tier), register it via the
+host, and have a live turn where flash actually calls it.
+- New unit `packages/tool-read-file/` (or `tools-fs/`): a `ToolContract`
+ (`name`, `description`, JSON-schema `parameters`, `execute` using `node:fs`,
+ workdir-contained — see the OLD repo's `read-file.ts` for the containment
+ guard, `/home/tradam/projects/dispatch/dispatch-source/packages/core/src/tools/read-file.ts`,
+ as a REFERENCE only — do not copy blindly).
+- Manifest with `capabilities: { fs: true }`.
+- host-bin registers it; session-orchestrator passes the tool set to `runTurn`.
+- **Live test:** ask the model to read a file → confirm a `tool-call` +
+ `tool-result` round-trip + final answer using the content.
+**This is the highest-value next step** — it's the first real validation that the
+turn loop's tool path works against a live model.
+
+### 3. Small CRs / hygiene (batch; mostly orchestrator wiring)
+- **host CR-1:** `createHost` should expose `getHostAPI()` so host-bin drops its
+ duplicate `HostAPI` adapter (`buildPostActivationHostAPI`). Kernel-host unit.
+- **storage-sqlite CR-2:** manifest declares `contributes.services:["storage"]`
+ but `activate` is a no-op (backend is a kernel dep passed via host-bin). Either
+ remove the misleading `contributes`, or make it provide the factory as a
+ service. Reconcile manifest vs reality.
+- **Stale detached server** may linger on an old port from earlier runs — harmless;
+ kill if it blocks a port.
+
+### 4. Vocabulary drift fix: `tabId` → `conversationId` ⟵ contract change
+**Problem (tracked in GLOSSARY.md "Known vocabulary drift"):** the canonical term
+is **`conversationId`**, but `AgentEvent`s and `RunTurnInput` still use **`tabId`**
+(the orchestrator bridges `conversationId → tabId`). This is pure P8 debt.
+**Goal:** rename `tabId` → `conversationId` across `events.ts` + `runtime.ts`
+contracts and every consumer.
+**Process (textbook §5.3 fan-out):** kernel-contracts owner renames the symbols,
+runs `lsp references` to get the TRUE consumer list, orchestrator dispatches the
+affected owners (runtime, session-orchestrator, transport-http, host-bin) to
+update. Do this as ONE coordinated change; expect ~4–5 files. Update GLOSSARY
+(remove the drift note) when done.
+
+---
+
+## Standing reminders (from ORCHESTRATOR.md — don't relearn the hard way)
+- Summon with `opencode run -m opencode-go/qwen3.7-max`, **inline the prompt via
+ `"$(cat prompts/X.md)"`** (the `-f` flag is greedy and breaks). Don't background;
+ large timeout.
+- **`deepseek-v4-flash` is the app's runtime testbench, NOT for building agents.**
+- Parallelize ONLY disjoint file sets (single-writer). Log parallel runs in tasks.md.
+- Verify independently (typecheck/test/check) + confirm single-lane edits. Trust
+ nothing until green yourself.
+- Keep `tasks.md` current; write decisions down before pivoting.
+- Be careful with destructive git; back up `notes/` before any reset/clean.
+
+## Open design decisions still parked (post-rewrite, from plan §8)
+- Persistent *waking* agents + wake-time contract-delta sync (we use fresh
+ summons for now).
+- These are NOT blocking the next steps above.
diff --git a/ORCHESTRATOR.md b/ORCHESTRATOR.md
index 6efc031..ba5be2e 100644
--- a/ORCHESTRATOR.md
+++ b/ORCHESTRATOR.md
@@ -126,7 +126,19 @@ the project-specific, non-inferable rules.
---
-## 4. Verification (trust nothing — re-run it yourself)
+## 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.
+
+**Pragmatic addendum (current phase):** while the system is young, ALSO skim the
+key files an agent produced — agents can report "clean" while making subtle
+contract mistakes, and catching them now is cheap. This is a deliberate,
+phase-appropriate overlay on the §3.6 protocol, not a replacement: the
+authoritative signals remain green typecheck + passing boundary tests + clean
+lint. As the harness matures, lean harder on the tests and less on reading code.
After every agent, independently:
```bash
@@ -169,6 +181,14 @@ git status --short # confirm the agent stayed in its lane (no out-of-scope edit
- **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.)
+- **`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,
diff --git a/packages/host-bin/src/main.ts b/packages/host-bin/src/main.ts
index 8a7dd57..a3eb164 100644
--- a/packages/host-bin/src/main.ts
+++ b/packages/host-bin/src/main.ts
@@ -127,7 +127,8 @@ async function boot(): Promise<void> {
const hostAPI = buildPostActivationHostAPI(host, deps);
const app = createServer(hostAPI);
- const port = Number(process.env.PORT) || 3000;
+ // Port precedence: BACKEND_PORT (the rewrite's assigned port) → PORT → default.
+ const port = Number(process.env.BACKEND_PORT) || Number(process.env.PORT) || 24203;
const server = Bun.serve({ fetch: app.fetch, port });
logger.info(`Dispatch listening on http://localhost:${server.port}`);
}