Why there is both a handler.mjs and a harness.mjs (the streaming constraint) (Ollama Capabilities)

**Note** from Bead: Ollama Capabilities · [canonical source](https://redfish.acequia.io/guerin/.agents/70758adc-2d58-40f5-a294-443dfb255209/2026-06-29/notes/01-handler-vs-harness-streaming.md) · session 2026-06-29 · discussion: Talk: Ollama Capabilities

The user asked for a `harness.mjs` and to mount a recent bead about how `handler.mjs` works. Building it surfaced a real architectural fork, worth recording.

## The constraint The filesystem router on `stephenguerin.live` ([`server.js`](c:/Users/steph/Documents/sites/redfish.acequia.io/guerin/.agents/82bd6fa4-4d5a-435b-9deb-ca930a82aa89/handler.mjs) hot-loads the deepest `handler.mjs`) returns a handler result via: ```js function send(httpRes, status, headers, body) { httpRes.writeHead(status, headers || {}); httpRes.end(body); } ``` `res.body` is a **string/Buffer ended in one shot**. There is no hook to write a chunked/streamed body from a handler. So a `handler.mjs` **cannot stream LLM tokens** — it can only return the whole answer once it has it. A token-by-token demo is the *point* of an LLM harness, so a pure-proxy handler would have thrown away the best part.

## The split - **`harness.mjs`** is a **browser** ES module. When the page is on `localhost` (the home machine), it talks **directly** to `http://localhost:11434` and consumes the NDJSON stream itself → real streaming. Ollama's default `OLLAMA_ORIGINS` already allows `localhost` on any port, so no CORS config is needed. - **`handler.mjs`** is the **server** deploy-home module. It (a) serves the static demo via `passthrough`, and (b) provides a **buffered same-origin proxy** at `/ai/ollama/api/*` → `127.0.0.1:11434`. This is the only way the demo can work **over the tunnel**, where the viewer's browser cannot reach the *server's* localhost (and Ollama is local-only by design). The handler forces `stream:false` upstream and returns the single JSON object. `harness.mjs::defaultEndpoint()` picks the mode automatically from `location.hostname`; the UI lets you override (auto / direct / proxy).

## This honors the rung-0 rule The handler **runs no model and holds no key** — it negotiates, serves files, and proxies to a local daemon. That is exactly the cognition-rung-0 invariant of the canonical router-bead ([`82bd6fa4`](https://redfish.acequia.io/guerin/.agents/82bd6fa4-4d5a-435b-9deb-ca930a82aa89/about.md)): the deterministic edge does routing/negotiation; *thought* lives elsewhere (here, the local Ollama process rather than an inward-connecting cognition client). The model is just another resource the deterministic layer routes to.

## Open thread If we ever want streamed tokens **through** the origin (proxy mode), the router's `send()` would need a streaming-body path (return a `ReadableStream`/async iterator, or a `res.stream` flag the runtime pipes). That is a `server.js` change → one elevated restart, out of scope for this bead. Filed as a pointer for the filesystem-router bead.

## References (bead cross-links) - Bead: Stephenguerin.live Router Bead · [canonical](https://redfish.acequia.io/guerin/.agents/82bd6fa4-4d5a-435b-9deb-ca930a82aa89/)