summaryrefslogtreecommitdiffhomepage
path: root/tasks.md
blob: 8da0bcc2eeb9b77f1a6f83fb4ac300aa707b5abc (plain)
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
# MVP Tasks — kernel + core extensions → working `curl` turn

Goal: send a message via HTTP `curl` and get a real multi-turn response from
OpenCode Go (flash), through the **actual architecture** (full fidelity: every
core feature is an extension loaded via manifest through the host).

Contracts are referenced **statically as types** (so `tsc` + `lsp references`
drive fan-out); extension **loading is dynamic** (manifests via the host).

## Legend: [ ] todo  [~] in progress  [x] done (verified + committed)

### Kernel
- [x] **contracts** — the ABI. `fd855ff` (+ `974ce6f`)
- [x] **bus** — event/hook/service bus. `669c269`
- [x] **runtime** — `runTurn` turn loop (dispatch §3.3), 16 tests. `ae22da5`
- [x] **host** — discovery→DAG→activate→registries; builds HostAPI (wraps bus). `dbcf219`
- [x] **kernel-crs** — CR-2 (HostAPI.getProviders/getTools), CR-3 (runTurn tabId/turnId). `e56b591`

### Core extensions (each ships a real manifest, loaded via the host)
- [x] **storage-sqlite** — bun:sqlite StorageNamespace + migrations. `9b611d6`
- [x] **auth-apikey** — pure resolver env → ApiKeyCredentials. `9b611d6`
- [x] **provider-openai-compat** — OpenAI-compatible SSE → ProviderEvents. `9b611d6`
- [x] **conversation-store** — append-only persistence + pure reconcile. `8b9cc0c`
- [x] **session-orchestrator** — load history → resolve provider/tools → runTurn → persist. `357ad35`
- [x] **transport-http** — Hono `/chat` NDJSON stream. `357ad35`

### Integration
- [x] **host-bin** — boot: config (.env), discover+activate extensions, Bun.serve. `bf52b11`
- [x] **curl smoke test** — ✅ VERIFIED LIVE against OpenCode Go flash (opencode-1 key).

## ✅ MVP ACHIEVED (verified live)
- Single turn: `curl -d '{"message":"Say hello in exactly 3 words."}'` → `"Hello my friend"`.
- **Multi-turn:** `conversationId` threads history — turn 1 "remember PINEAPPLE" →
  turn 2 "what was the secret word?" → `"PINEAPPLE"`. ✓ (target B, §2.8)
- Full path: curl → transport-http → session-orchestrator → host/registry →
  provider-openai-compat → OpenCode Go flash → ProviderEvents → AgentEvents → NDJSON.
- 178 vitest tests pass; `tsc -b` clean; biome clean.

### API note (for callers)
- Chat request field is **`conversationId`** (not `tabId`) to thread multi-turn.
  Omit it → a fresh conversation (random id) each call.

## Open items (post-MVP, not blocking)
- **auth-apikey is vestigial:** provider reads creds from `host.config`, does NOT
  yet use `AuthContract.resolve()`. Wire auth→provider properly (see host-bin CR).
- **host CR-1:** `createHost` should expose `getHostAPI()` so host-bin drops its
  adapter duplication.
- **storage-sqlite CR-2:** manifest declares `contributes.services:["storage"]`
  but activate is a no-op (backend is a kernel dep). Reconcile manifest vs reality.
- **Stale detached server** on port 18390 from an earlier run — harmless, ignore/kill.
- Tools path unexercised by MVP (turn completes with `tools: []`). Add a tool ext next.

## Parallelization log
- host + conversation-store ran in parallel (disjoint). ✓
- session-orchestrator + transport-http ran in parallel (disjoint). ✓
- kernel-crs solo (touched shared contracts/extension.ts).

---

## Post-MVP backlog (orchestrator: mimo-v2.5-pro summons)

### Step 1 — Wire auth → provider properly  [x] DONE (verified live)
Design: provider self-registers in `activate` (full-fidelity), so it must reach
the `AuthContract` via the HostAPI. HostAPI exposes `getProviders`/`getTools` but
NOT auth. Fix = small contract addition mirroring the existing provider/tool
precedent.
- **kernel-host** (owner of `host.ts`/`extension.ts`/`host.test.ts`): add
  `getAuthProviders()`/`getAuthProvider(id)` to `HostAPI` + implement in
  `buildHostAPI()` + tests. `lsp references` drives fan-out.
- **provider-openai-compat** (owner): in `activate`, resolve creds via
  `host.getAuthProvider("apikey").resolve()` (ApiKeyCredentials → baseURL/apiKey)
  instead of reading `host.config` apiKey/baseURL. Add `dependsOn:["auth-apikey"]`.
  Model stays config-driven (not a credential).
- **host-bin** (orchestrator CR wiring): mirror the two getters in
  `buildPostActivationHostAPI`.
Sequence: kernel-host FIRST (adds contract surface), THEN provider (consumes it),
THEN host-bin wiring. NOT parallel (provider depends on kernel surface).

**Step 1 RESULT:** done + verified. kernel-host added `getAuthProviders`/
`getAuthProvider` to HostAPI; provider-openai-compat `activate` now resolves creds
via `host.getAuthProvider("apikey").resolve()` (`dependsOn:["auth-apikey"]`);
host-bin stub mirrors the getters. 185 tests, typecheck+biome clean. Live curl
returned a real response with auth-apikey on the path (boot log shows auth-apikey
activates before provider; provider registered = creds resolved through contract).
NOTE: host-bin's `buildPostActivationHostAPI` stub is slated for removal in Step 3
(host CR-1). Summons: prompts/step1-kernel-host.md, prompts/step1-provider.md (mimo-v2.5-pro).

### Step 2 — First TOOL extension (read_file)  [x] DONE (verified live)
New unit `packages/tool-read-file/` (owner-agent, mimo-v2.5-pro). Pure-core/shell
split: `createReadFileTool(workdir)` → `ToolContract` named `read_file` (offset/
limit pagination, 1-indexed; two-layer workdir containment incl. realpath symlink
guard); `activate` calls `host.defineTool`. 29 unit tests. session-orchestrator's
`resolveTools: () => [...host.getTools().values()]` flows it into runTurn for free.
Orchestrator wiring CRs (done): root tsconfig ref, host-bin dep + import +
CORE_EXTENSIONS (before session-orchestrator), bun install, biome import-sort.

**Step 2 RESULT:** done + verified. typecheck clean, **214 tests pass** (185→+29),
biome clean. LIVE: booted on 24203, asked flash to read a test file → stream
contained a real **`tool-call` + `tool-result`** round-trip and the final answer
quoted the file's secret passphrase (MAGENTA-OTTER-42) correctly. The kernel
tool-dispatch loop is now proven end-to-end against a live model (§3.3). Summon:
prompts/step2-tool-read-file.md, report: reports/step2-tool-read-file.md.

### Step 3 — Small CRs / hygiene  [x] DONE (verified live)
Parallel summons (disjoint file sets, mimo-v2.5-pro; output→reports/*.run.log):
- **host CR-1** (kernel-host owner): `createHost` exposes `getHostAPI()` (registration
  closed post-activation) so host-bin drops its `buildPostActivationHostAPI` duplicate.
  prompts/step3-kernel-host.md. Files: packages/kernel/src/host/{host.ts,host.test.ts}.
- **storage-sqlite CR-2** (owner): remove false `contributes.services:["storage"]`
  from manifest (backend is a kernel bootstrap dep via HostDeps.storageFactory, not a
  bus service); document the intentional no-op activate. prompts/step3-storage-sqlite.md.
  Files: packages/storage-sqlite/src/extension.ts.
After both land: orchestrator wires host-bin to use `host.getHostAPI()` (deletes adapter).

**Step 3 RESULT:** done + verified. (1) kernel-host: `createHost` now exposes
`getHostAPI()` returning the canonical post-activation HostAPI with registration
methods closed (single builder w/ `registrationClosed` flag — zero duplication);
+4 host tests. (2) storage-sqlite: removed false `contributes.services:["storage"]`;
documented the intentional no-op activate (backend is a kernel bootstrap dep via
HostDeps.storageFactory). (3) host-bin wiring (orchestrator): deleted the
`buildPostActivationHostAPI` adapter, now calls `host.getHostAPI()`; dropped the
now-unused `HostAPI` import. typecheck clean, **218 tests pass**, biome clean.
Live boot: all 7 extensions activate, curl returns real responses. Summons:
prompts/step3-{kernel-host,storage-sqlite}.md (mimo-v2.5-pro, parallel, disjoint).

### Step 4 — Vocab drift: tabId → conversationId  [x] DONE (verified)
Interlocked contract rename (every consumer breaks at once) → ONE coordinated
multi-file owner-agent (mimo-v2.5-pro, output→reports/step4-*.run.log) owns the
full 10-file set; orchestrator updates GLOSSARY + tasks separately.
Files: contracts/{events,runtime}.ts, runtime/{events,run-turn,dispatch,run-turn.test}.ts,
session-orchestrator/orchestrator.ts, transport-http/{app,app.test,logic.test}.ts.
External `/chat` field is ALREADY conversationId — unchanged; only the internal
field + emitted NDJSON event field flip tabId→conversationId. prompts/step4-rename-conversationid.md.

**Step 4 RESULT:** done + verified. The agent renamed `tabId` → `conversationId`
across all 10 files in one atomic change: all 11 `AgentEvent` variants
(`contracts/events.ts`) + `RunTurnInput` (`contracts/runtime.ts`), the runtime
event factories/`run-turn`/`dispatch`, the session-orchestrator bridge (dropped
the redundant `tabId: conversationId` line), transport-http emit wiring, and all
three test files (inputs + assertions). Doc comments updated ("turn/tab identity"
→ "turn/conversation identity"; StatusEvent "tab / session" → "conversation").
Orchestrator re-verified independently: **typecheck clean (EXIT 0)**, **218 tests
pass** (unchanged count — pure rename, no behavior change), **biome clean**, and
`grep tabId packages/` → **zero matches**. Agent stayed in-lane (exactly the 10
owned files). External `/chat` request field + `X-Conversation-Id` response header
untouched (already canonical); emitted NDJSON events now carry `conversationId`.
GLOSSARY drift note removed. Report: reports/step4-rename-conversationid.md.

---

## 🏁 Post-MVP backlog COMPLETE (Steps 1–4 all done + verified)
All four ordered HANDOFF steps landed: auth→provider seam wired, first live tool
extension (read_file), hygiene CRs (getHostAPI + manifest honesty), and the
tabId→conversationId vocab rename. typecheck + biome clean; 218 tests green.
Remaining work is the parked design decisions (persistent waking agents, etc.) —
non-blocking. See HANDOFF.md "Open design decisions still parked".

---

## Observability — Phase A (logging substrate) ✅ DONE + verified live
Goal: structured logs + spans captured durably to a journal file — the substrate
for the agent-first observability subsystem (design: notes/observability-design.md).

- [x] **Unit 1 — kernel-logging** (mimo-v2.5-pro): Logger/Span ABI
  (`contracts/logging.ts`) — leveled/attributed/auto-scoped (host stamps
  `extensionId`), incremental span records (open/close, crash-reconstructable, D3),
  injected `LogSink` (pure record-builder). `ctx.log` on ToolContract; runTurn
  opens turn/step/tool-call spans + the verbatim **"before"** prompt on the step span.
- [x] **Unit 2 — journal-sink** (`packages/journal-sink/`, mimo-v2.5-pro): bootstrap
  `LogSink` → NDJSON append-only journal (pure `serialize` + thin fs edge, rotation,
  fail-safe drop — never blocks a turn). NOT an extension (HostDeps bootstrap dep).
- [x] **Orchestrator fan-out + wiring** (direct): bus error-attrs `{ err }`,
  FakeLogger/`ctx.log` test conformance; host-bin injects `logSink`+`logDeps`
  (journal at `.dispatch/journal/`); session-orchestrator threads `host.logger`
  (childed per turn) into runTurn.

**Result:** typecheck clean, **250 tests** (218 → +22 journal-sink, +10 kernel),
biome clean. **Live boot verified:** a turn's journal contains host logs + turn/step
spans (open+close) + the `prompt:before` record carrying the verbatim messages array
— the pre-mutation prompt is fully reconstructable. 2-process model: app + in-process
sink → journal file; the collector (process 2) is Phase B. Redaction is
per-extension self-redaction (no shared helper — isolation over DRY).

### Next (observability)
- **"AFTER" capture** — `provider.request` verbatim post-transform in
  provider-openai-compat → full round-trip rebuild + before↔after diff (§10).
- Minor refinement: move the large `prompt:before` payload from `attributes` into the
  record `body` field (store-fat-serve-thin) — currently a stringified attribute.
- Phase B: out-of-process collector → SQLite store + query (§11).

Summons: prompts/phase-a-{kernel-logging,journal-sink}.md;
reports/phase-a-{kernel-logging,journal-sink}.md.