writeonce/docs/stories/jarvis/01-chat-loop.md
shoney.arickathil cf31bb4ebd docs(rv2-tls,jarvis,status): outbound TLS client complete — jarvis unblocked
- rv2 9 §F3c-net marked LANDED + live-gated; phase-F row COMPLETE (client);
  frontmatter review_pending updated (client complete, remaining = G server
  + deferred park-handshake/TlsConn/pooling + doctrine-doc corrections)
- jarvis 00-story + 01: the outbound-TLS blocker is cleared
  (net.connect_tls landed) — jarvis 1 (chat loop) is now buildable
- status board: rv2 9 row + NEXT PLAN rewritten to the completed client;
  next step is jarvis 1 or rv2 9 G

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 732c2216b501dd226d306f9abcbd1ddd846809dc)
2026-09-15 01:15:52 +02:00

99 lines
5.1 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.
## The outbound seam has landed
The blockers are cleared: `net.connect` (✅) and runtime-v2
[9](../runtime-v2/09-in-process-tls.md)'s **`net.connect_tls` / `net.read_tls` /
`net.write_tls`** (✅ landed 2026-09-09, live-gated `just tls`) expose outbound
HTTPS from `.wo`, with the full hand-rolled TLS 1.3 handshake + chain/hostname
validation. Everything below is now buildable `.wo` on top of that seam plus
porch 2/3/6/7. (Note: the TLS builtins return the connection as an `Int` fd, per
the F3c-net object-model decision — the chat loop dials with them directly.)
## 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`.