- jarvis 1 (chat loop) brainstormed to ready with forks AUTO-APPROVED for autonomous execution and flagged in `review_pending` frontmatter for the developer's second review: Anthropic Messages API backend, env-var API key, actor-per-conversation SSE relay, durable @table history, session-gated routes - jarvis 2 (tool use) + 3 (retrieval/RAG) created at refine with forks named - 00-story iterations table linked to the new files - blocked until rv2 9 TLS reaches phase F; pure .wo on porch 2/3/6/7 + the seam (cherry picked from commit 8e160c3fcf9c50a05af073c036c693b192c9e595)
96 lines
4.9 KiB
Markdown
96 lines
4.9 KiB
Markdown
---
|
||
track: jarvis
|
||
iteration: "1"
|
||
status: pending
|
||
readiness: ready
|
||
review_pending: "forks auto-approved 2026-09-08 for autonomous execution — developer second review before code lands"
|
||
---
|
||
|
||
# jarvis 1 — the chat loop: a prompt in, streamed tokens out, durable history
|
||
|
||
> Part of [Story — jarvis, the writeonce AI assistant](00-story.md).
|
||
> The whole end-to-end seam: a browser sends a prompt, jarvis relays it to an
|
||
> LLM over HTTPS, streams the reply back token by token, and persists the
|
||
> conversation durably. Nothing past this rung is worth building until it runs.
|
||
>
|
||
> **Forks auto-approved (2026-09-08) for autonomous execution and flagged in
|
||
> `review_pending` for the developer's second review** — the defaults below are
|
||
> reasonable but were not individually confirmed.
|
||
|
||
## Blocked until the outbound seam lands
|
||
|
||
This iteration cannot run until `net.connect` (✅ landed) and runtime-v2
|
||
[9](../runtime-v2/09-in-process-tls.md) TLS reach **phase F** (`net.connect_tls`
|
||
+ the handshake). Crypto A–D are landed; E (X.509) and F (handshake) remain.
|
||
Everything below is buildable `.wo` on top of that seam plus porch 2/3/6/7.
|
||
|
||
## Decisions locked (auto-approved, review pending)
|
||
|
||
1. **Backend: the Anthropic Messages API over HTTPS**, streaming (SSE). One
|
||
backend for v1; a thin adapter boundary so an OpenAI-compatible backend can
|
||
slot later without touching the chat loop. Endpoint, model id and version
|
||
header come from config.
|
||
2. **Auth: an API key from the environment/config**, sent as the `x-api-key`
|
||
header — never in a URL, never logged. Missing key is a startup refusal, not
|
||
a runtime surprise.
|
||
3. **The relay is an actor per conversation.** A conversation actor owns the
|
||
upstream `net.connect_tls` connection, parses the LLM's SSE
|
||
(`content_block_delta` text deltas), and forwards each delta to the browser
|
||
through porch [7](../porch/07-sse-and-compression.md)'s SSE. Backpressure and
|
||
disconnect cleanup follow porch 7's contract (a write to a gone client frees
|
||
the fiber, the upstream fd and the actor).
|
||
4. **Durable history in two `@table` classes**: `Conversation { id @unique,
|
||
principal, created_at }` and `Message { conv_id (indexed), seq, role,
|
||
content, created_at }`. Keyed to the session principal (porch
|
||
[3](../porch/03-sessions.md)); history replays after a restart with no
|
||
external store — the writeonce differentiator.
|
||
5. **Route surface, session-gated**: `GET /` (chat UI via wo-html/writeonce-view),
|
||
`POST /message` (accept a prompt, append it, start the stream), `GET /stream`
|
||
(SSE of the reply). The POST is CSRF-protected once porch
|
||
[4](../porch/04-csrf.md) is built; until then it is bearer/session-gated and
|
||
the gap is stated.
|
||
6. **One turn at a time, no tools.** No function-calling, no retrieval — those
|
||
are iterations 2 and 3. The reply is a single streamed assistant message.
|
||
|
||
## Phases
|
||
|
||
- **A — the backend client.** Over `net.connect_tls`, issue the Messages
|
||
request and parse the streamed SSE deltas into text. Adapter boundary isolated
|
||
so the wire format is one file.
|
||
- **B — the conversation store.** The two `@table` classes, append-message,
|
||
load-history, list-conversations; durable-replay proven.
|
||
- **C — the relay + web surface.** The conversation actor, the porch routes, the
|
||
wo-html chat page, browser SSE of the reply; session-gated.
|
||
- **D — the gate and the ledger.** `just` a jarvis sample end to end against a
|
||
**local stub** LLM server (no network in the gate): prompt → streamed reply →
|
||
durable history → restart-replay. Both the happy path and a mid-stream
|
||
disconnect. Record what landed.
|
||
|
||
## Acceptance Criteria
|
||
|
||
- **Given** a signed-in user and a prompt, **when** it is sent, **then** the
|
||
reply streams back token by token and both prompt and reply are persisted.
|
||
- **Given** a restart, **when** the user returns, **then** their conversation
|
||
history replays from the `@table`, no external store.
|
||
- **Given** the client disconnecting mid-stream, **when** the next delta
|
||
arrives, **then** the upstream fd, the fiber and the actor are freed.
|
||
- **Given** a missing API key, **when** the app starts, **then** it refuses
|
||
loudly rather than failing at first request.
|
||
- **Given** the gate, **when** it runs, **then** it exercises the whole path
|
||
against a local stub with no live network call.
|
||
|
||
## Out Of Scope
|
||
|
||
- **Tool use / function calling** — iteration [2](00-story.md).
|
||
- **Retrieval / embeddings** — iteration [3](00-story.md).
|
||
- **Multiple backends at once / model routing** — sketched in the overview,
|
||
later.
|
||
- **The live network in the gate** — the gate uses a local stub server; a real
|
||
API smoke test is a manual, keyed, out-of-gate step.
|
||
|
||
## Info
|
||
|
||
Pure `.wo` on porch 2/3/6/7 + the TLS seam; no new runtime work of its own. The
|
||
one hard dependency is runtime-v2 9 reaching phase F. The auto-approved forks
|
||
(backend choice, auth transport, store schema, route surface) are the developer
|
||
second-review items flagged in `review_pending`.
|