openspec/changes/archive/2026-09-10-add-parakeet-api/reviews/2026-09-10-re-advise.md
Re-advise: add-parakeet-api
ADVISE: accept READER: agt-fable-5-1-arch-review SPAWN: /Users/dukejones/work/ClientProjects/AllSystemsGo/AICamera/.spawns/aicam-parakeet-shape-1789079938-41901-4c435cab
Rigor: architecture. Density: standard. Reader family: Claude (Fable 5.1). Author family: grok.
Prior: reviews/2026-09-10-advise.md (send-back, five spec lines + one struck design clause).
Amend under review: 4194d0f.
Read order: packet Why → proposal.md → steer.md → change spec specs/parakeet-asr/spec.md →
prior review → daBOM src/lib/server/api/app.ts:657 (servers: [{ url: '/api/v1' }]) and root.ts:30-37 (well-known trio). Blind take written before design.md, tasks.md, and the
sibling deltas were opened. No living parakeet-asr spec exists; capability is ADDED at fold.
Blind take (written before design.md / tasks.md)
- Pin, held.
servers/api/v1is now a SHALL in Well-known, and the first scenario checks it. The document is self-locating. This was the one line that turned “same bytes” into a contract; it is there. - Pin, held. Sidecar has no network listener; Bun is the sole tailnet socket; the
sidecar has no portscenario isss/lsof-checkable. The Tailscale-bind requirement now covers both processes, not “the ASR process” singular. - Pin, held. CPU-only is mechanical:
CUDA_VISIBLE_DEVICESempty,CPUExecutionProvider, intra-op ≤ 4, and a scenario that runs a clip beside the 30 fps stack. The cost on this box is the CPU steal, and the spec now names it. - Pin, held.
GET /api/v1/healthreportswarm | cold | down, provider, model id, and is listed in the document. The 503 and the GPU-idle scenarios both have a cheap probe. - Pin, held. Stream is NDJSON
{text, t0, t1, final}, non-browser v1, listed in the document, last eventfinal: true. One response type per operation, ascreateRouteneeds. - Refuse, held. No resample. “Wrong sample shape SHALL be 422” stands alone; the “or resample in the sidecar” escape is gone from the spec. Must confirm it is also struck from design, since the engine actor reads design.
- Refuse, held. 415 for non-audio content-type, 422 for declared-but-wrong shape. The two are separated as asked.
- Would pin, minor. Health SHOULD answer 200 in every sidecar state with the state in the body. A
/healththat returns 503 when the sidecar is cold is indistinguishable from Bun being down, which is the one thing the probe exists to separate. The spec does not say the status code; the hono actor will pick one. One clause. - Would refine, minor, my own wording. “End-of-stream is connection close” came from my prior take and the author copied it. If the client closes the TCP connection the server cannot deliver
final: true. The precise statement is: end-of-input is end of the request body (half-close); the server then emits the last event withfinal: trueand closes the response. This is a stream-sibling implementation detail; the intent is unambiguous. - Tradeoff, accepted. Raw PCM on
POST /transcribeneeds a declared content-type so the 415 rule is clean;audio/L16; rate=16000; channels=1(RFC 2586) orapplication/octet-streamnamed in the document. “As declared in the spec” delegates this to the OpenAPI document, which is the contract. Fine, the document decides, not prose.
Compare against design.md, tasks.md, sibling deltas
Opened after the take: design.md, tasks.md, add-parakeet-{engine,hono,stream} spec deltas
and tasks, sim/3090/serve.py bind_ip() (env → tailscale ip -4 → 100.103.147.70).
Every send-back item landed in the spec, and design agrees with it.
| Send-back item | Spec (folds) | Design (does not fold) |
|---|---|---|
| 1. Resample contradiction | “Wrong sample shape SHALL be 422”, no escape | “The sidecar does not resample or mix channels.” Struck. |
| 2. Health op | Clip transcribe: GET /api/v1/health warm\|cold\|down, provider, model id, listed | Not repeated; not needed. |
| 3. Sidecar listener | Tailscale bind: “SHALL NOT open a network listener … child on stdin/IPC. Bun SHALL be the sole tailnet socket” + sidecar has no port scenario | Same sentence. Engine tasks: “Bun child on stdin/IPC — no TCP/UDP listener”. The “Tailscale-adjacent unit or” is gone. |
| 4. CPU env + threads + CPU scenario | CPU-only: CUDA_VISIBLE_DEVICES empty, intra-op ≤ 4, transcribe beside 30 fps plugins scenario | CUDA_VISIBLE_DEVICES=, providers=["CPUExecutionProvider"], four threads. |
5. servers | Well-known: “SHALL set servers to the /api/v1 base”; scenario checks it | Table row: servers: [{ url: '/api/v1' }]. Matches daBOM app.ts:657. |
| note: NDJSON | Audio stream pipe: NDJSON {text, t0, t1, final}, final: true last, non-browser v1 | NDJSON only; “v1 stream clients are non-browser”. |
| note: 415 vs 422 | Both codes, separated | Both codes, separated. |
The hono sibling’s scenario that accepted ”/transcribe or /api/v1/transcribe as declared” now
reads ”servers includes /api/v1, and paths includes /transcribe and /health”. The
ambiguity the take was about is closed across the DAG, not just in this change.
Steelman against the take: the take’s remaining items (8, 9, 10) are each one clause an
implementer can get right from the spec’s intent, and none of them is a line that would fold
wrong. Item 8 (health status code) is the strongest; a /health that 503s when cold does not
break any stated scenario, it only weakens the probe. That is a hono-node test, not a shape
send-back. Item 9 is my own phrase; the author should not be sent back for copying the reader.
Item 10 is delegated to the OpenAPI document by design, which is the point of the change.
Notes, not blocking. Addressed to the sibling actors and the folder.
- Health status code (hono).
GET /api/v1/healthSHOULD return 200 in every sidecar state with the state in the body. Reserve 503 for/transcribe. Otherwise “sidecar cold” and “Bun down” look the same to a probe. Worth one clause at fold under Clip transcribe. - Half-close, not connection close (stream). Read “end-of-stream is connection close” as: the
client ends the request body; the server emits the last event with
final: true, then closes the response. If the client tears down TCP first, nofinalcan arrive and that is the client’s fault, not a failed scenario. Stream tasks still say “NDJSON or SSE{text, t0, t1}” and “chunked POST or WS”; the shape spec has decided both. NDJSON withfinal, and chunked POST, because OpenAPI 3.1 cannot describe a WebSocket and the spec says the stream operation SHALL appear in the well-known document. WS is refused by the shape spec, not by this note. - Workspace vs own lockfile (hono). Hono tasks say “Bun workspace
asr/”. A rootworkspacesentry shares the rootbun.lockwith the Pages build, which is the graph the prior take refused to grow. Preferasr/with its ownpackage.jsonand lockfile, no root workspace membership. The spec line “SHALL NOT be mounted inside the SvelteKit adapter-static app” is satisfied either way; this is about the lockfile, not the mount. - Raw PCM content-type (hono). Declare it in the document (
audio/L16; rate=16000; channels=1orapplication/octet-stream) so the 415 rule has a clean edge. Prose says “as declared in the spec”; the document is where it gets declared. - Bind resolution (hono). Default
100.103.147.70in the spec is fine; resolve likeserve.pybind_ip()(env →tailscale ip -4→ default) and never fall through to0.0.0.0. Design already says never0.0.0.0. - Fold order (folder). Unchanged from the prior review. This change carries the full capability; engine “Warm CPU sidecar”, hono “Well-known spec is served”, stream “Stream listed in well-known” are each a subset of a requirement here. Fold this change first; fold the siblings as scenarios under its requirements, not as parallel requirements.
ARCHITECTURE.mdnote is owed at fold per tasks; design.md is the draft. Fine.
Verdict. Accept. The five spec lines and the struck design clause from the send-back are all present, spec and design say the same thing, and the sibling deltas no longer contradict the shape. Nothing left here folds wrong. Remaining notes are sibling-node implementation clauses.