← Context/ specs
Request Reviewer — the Pre-Flight Surface
See exactly what is about to leave the building — and guarantee the request you reviewed is byte-identical to the one that fires.
What this is
request-reviewer is the third federated remote of augment-it, the pre-flight
inspection stage of the enrichment pipeline. Today it is an empty apps/
directory with a one-line README. This spec defines what gets built into it.
Pipeline position:
prompt-template-manager ──▶ request-reviewer ──▶ [ model call ] ──▶ response-reviewer ──▶ highlight-collector ──▶ insight-manager
(author the prompt) (PRE-flight: this spec) (POST-flight) (distill) (land in CRM)request-reviewer is the pre-flight counterpart to the response-reviewer.
Where Why Response Reviewer and Highlight Collector Exist governs
inspecting what came back, this spec governs inspecting what is about to go
out. The README's framing — "reviewing requests to make sure the Prompt
Template is appropriately applied" — is exactly right; this spec makes it
concrete.
Three things it does
Show the resolved request. The authored prompt template with every
{{token}}replaced by the matching property value from a real record row. This is the proven part — Tanuj's build already did template-with- values (Tanuj Request Reviewer As Built, thegenerateDefaultPromptlogic). augment-it's{{token}}model supersedes Tanuj's auto-generated default prompt, but the idea — "render the request with the data filled in" — is prior art that worked.Pick the model. A toggle choosing which model the request fires against. This is net-new — today the model is a server-side env var.
Show the JSON request. An optional toggle revealing the literal API request body —
{ model, max_tokens, messages, tools }. Net-new. Rationale: provider API request shapes get richer as their docs evolve; seeing the literal body is a learning and debugging surface.
What it does NOT do (non-goals)
It does not render or handle the model's response — that is the response-reviewer's job.
It does not edit prompt templates — that is prompt-template-manager.
It does not extract highlights or write back to a CRM.
It does not inspect the model's response — but firing exits directly to the response-reviewer, the post-flight pair built alongside this one. See the companion spec Response Reviewer and Response Store.
The core architecture decision — one request builder, two callers
This is the load-bearing decision. Everything else is UI.
Today, the model request is built entirely inside prompt-runner:
services/prompt-runner/src/template.ts—fillTemplate(content, fields)does the{{token}}substitution.services/prompt-runner/src/anthropic.ts—runPrompt(filledPrompt, tools)assembles the Anthropicmessages.create()body inline and sends it. The model isprocess.env.LLM_MODEL ?? 'claude-opus-4-7', fixed per container.
request-reviewer needs to show that request before it fires. There are two ways to get the resolved request in front of the user, and only one is acceptable:
| Approach | Verdict |
Reimplement fillTemplate + body assembly in the request-reviewer frontend | ❌ Rejected. This is exactly the copy-pasted-generateDefaultPrompt smoking gun Tanuj Request Reviewer As Built calls out. A frontend copy drifts from the runner; the reviewer would eventually lie about what fires. |
| A service capability that builds the request without sending it; the frontend just displays the result | ✅ Chosen. One code path builds the request; the preview and the real fire both go through it. The previewed request is guaranteed identical to the fired request because it is the same function. |
The mechanism
Extract a pure
buildRequest()in prompt-runner — a newservices/prompt-runner/src/request.ts— that takes a filled prompt plus{ model, tools, max_tokens }and returns the exact Anthropicmessages.create()request object.runPrompt()is refactored to callbuildRequest()and then send; it no longer assembles the body inline.Add a
prompt.previewcapability. A new NATS subjectprompt.preview.requested, handled by prompt-runner. It takes(prompt_id, record_set_id, row_id, model?)and performs: fetch the prompt, fetch that one row,fillTemplate,buildRequest— and returns the result without ever calling Anthropic. No LLM call, no cost, fast.request-reviewer invokes
prompt.previewthrough the workspace WebSocket like any other capability, and displays what comes back.
┌──────────────────────────────────────┐
│ prompt-runner │
│ │
prompt.preview ──▶ │ fillTemplate ─▶ buildRequest ─▶ return│ (no send)
│ │ │
prompt.run ──▶ │ fillTemplate ─▶ buildRequest ─▶ send │ (fires)
│ │
└──────────────────────────────────────┘
one fillTemplate, one buildRequest,
two entry points — preview cannot drift from runModel selection — env var becomes a per-request argument
Today anthropic.ts pins the model: const MODEL = process.env.LLM_MODEL ??
'claude-opus-4-7'. The model toggle requires making model a per-request
choice.
The model registry
A small registry is the single source of truth the model toggle renders
from. It lives in packages/workspace as models.ts — every remote
already imports @augment-it/workspace, and prompt-runner (a standalone
Docker service) cannot consume a workspace package across its build context
anyway. prompt-runner does not need the registry: it accepts whatever
model string is passed and lets the Anthropic API validate it. (The first
draft said packages/config; that is a bare stub and unreachable from the
service build — packages/workspace is the pragmatic home.)
id | label | note |
claude-opus-4-7 | Opus 4.7 | Most capable. Default. Judgment-class enrichment. |
claude-sonnet-4-6 | Sonnet 4.6 | Cost-sensible for large per-row batches. |
claude-haiku-4-5-20251001 | Haiku 4.5 | Cheapest, fastest. Simple lookup-class enrichment. |
The note column maps loosely onto the lookup vs judgment distinction from
Original and Enhanced Record Instances — a hint, not an enforced rule.
Threading model through
prompt.previewandprompt.runboth accept an optionalmodel.buildRequest(the newrequest.ts) takesmodeland falls back to theLLM_MODELenv var when absent, so existing callers that pass nothing behave exactly as before.prompt-runner does not validate
model— it passes the string straight to the Anthropic SDK, which rejects an unknown model with a clear API error. The registry is the frontend's list, not a server gate.Persistence: the model choice is per-run and transient for this spec — request-reviewer holds it in component state, defaulting to the registry default. A future
default_model?field onPromptTemplate(so a prompt remembers its model) is flagged, not built — it is prompt-template-manager scope.
Max tokens — a free numeric field
max_tokens is the ceiling on the model's output, not a request size: you
are billed on tokens actually generated, so a generous ceiling the model
never reaches costs nothing. The real failure mode is setting it too low —
the response truncates mid-thought with stop_reason: "max_tokens".
request-reviewer exposes max_tokens as a free numeric input, defaulting
to 4096 (the current LLM_MAX_TOKENS env value, comfortable for
judgment-class output). The user types any value — lower for fast
lookup-class batches, higher for research-heavy web_search runs whose
narration and tool-call blocks eat the budget.
max_tokens threads exactly like model: an optional argument on
prompt.preview and prompt.run, defaulting to the env var when absent,
passed straight into buildRequest. Because the previewed request_body
comes from that same buildRequest, the JSON view always shows the
max_tokens that will actually fire.
Row stepping — one UI for single-record and batch
Per the resolved design, request-reviewer serves both the single-record enrich flow and the batch path with one row-stepped UI.
A prompt run against N rows is N distinct filled requests. The reviewer holds
a (prompt, record_set, rowIndex) cursor and shows one row's resolved
request at a time, with prev/next stepping. Each step re-invokes
prompt.preview for that row.
Two fire actions exit the stage:
Fire this row — single-record. Dispatches
prompt.runwithrow_ids: [row_id]and the chosenmodel. This is the pre-flight surface for the per-record enrich control from Build the Shell Tiling and Peek Deck.Fire whole set — batch. Dispatches
prompt.runwithrecord_set_id,row_limit, andmodel, as prompt-template-manager does today.
Dependency — row_ids on prompt.run
"Fire this row" needs prompt.run to accept a row_ids argument.
run.ts currently selects rows by rows.slice(0, limit). The minimal change:
when row_ids is present, filter to those rows instead of slicing by limit.
This row_ids input is small and stable, and this spec includes it.
Note the seam: Original and Enhanced Record Instances's record-instance
fold will later change what prompt.run does with the result (accumulate
into one enhanced instance vs. mint a new derived set). The row_ids input
is orthogonal to that — the fold reshapes the output, not the input. Adding
row_ids now does not pre-empt the fold; it is the input side the fold
assumes already exists.
The UI
A single-pane layout, themed entirely with the var(--color-*) / var(--fx-*)
tokens from 2026 05 21 06 Three Mode Theme System — no hardcoded color.
┌─ Request Reviewer ───────────────────────────────────────────┐
│ Prompt: [ Company Summary ▾ ] │
│ Records: [ Master Pipeline Tracker ▾ ] Row [ ◀ 3 / 25 ▶ ] │
│ Model: ( Opus 4.7 ) ( Sonnet 4.6 ) ( Haiku 4.5 ) │
│ Tokens: max_tokens [ 4096 ] │
│ View: [ Resolved prompt ] [ JSON request ] │
├───────────────────────────────────────────────────────────────┤
│ │
│ ── Resolved prompt ── │
│ Summarize the company «Acme Corp» operating in the │
│ «Logistics» sector. Their website is «acme.example». │
│ ▲ {{Company}} ▲ {{Sector}} ▲ {{Website}} │
│ │
│ ── Token binding ── │
│ {{Company}} → "Acme Corp" ✓ bound │
│ {{Sector}} → "Logistics" ✓ bound │
│ {{Website}} → "acme.example" ✓ bound │
│ {{Founded}} → (no such column) ✗ unbound │
│ │
├───────────────────────────────────────────────────────────────┤
│ [ Fire this row ] [ Fire whole set · limit 25 ]│
└───────────────────────────────────────────────────────────────┘Resolved prompt view (default) — the filled template as prose, with each substituted span visibly marked so the eye catches what came from data versus what was authored.
JSON request view (toggle) — the exact
messages.create()request object, pretty-printed:JSON{ "model": "claude-opus-4-7", "max_tokens": 4096, "messages": [{ "role": "user", "content": "Summarize the company …" }], "tools": [{ "type": "web_search_20260209", "name": "web_search" }] }toolsappears only when the prompt declaresweb_search— it is set in prompt-template-manager and shown here read-only, never toggled per-run, so the stored prompt and the fired request cannot diverge.max_tokensreflects the free numeric field above.Token binding panel — every
{{token}}in the template, its resolved value, and a bound/unbound mark. An unbound token (no matching column in the record set) is the pre-flight failure the reviewer exists to catch; the fire buttons disable while any token is unbound, mirroring the bind checkrun.tsalready enforces server-side.
Entry points
request-reviewer is reachable two ways:
Standalone — its own pickers (prompt dropdown, record-set dropdown, row stepper), fully self-sufficient as a remote.
Handoff — it listens for a
augment-it:review-requestwindow event carrying{ prompt_id, record_set_id, row_id? }, so prompt-template- manager or the per-record enrich control can hand off a pre-selected target. Same window-event pattern asaugment-it:enrich-recordfrom Build the Shell Tiling and Peek Deck.
Co-existence pairing
request-reviewer pairs naturally with prompt-template-manager in the shell's
co-existence split — tweak the prompt on one side, watch the resolved request
update on the other. A PAIRINGS entry in shell/src/remotes.ts
(promptTemplateManager + requestReviewer) wires this; recommended but
deferrable to the last phase.
Service changes
services/workspace/src/capabilities.ts
Add
'prompt.preview': 'prompt.preview.requested'toCAPABILITY_TO_SUBJECT.Add a short timeout for it in
CAPABILITY_TIMEOUTS_MS(~5 000 ms — no LLM call, so it must be fast; the default 5 s is fine, so this is optional).prompt.runalready routes and already has its 600 000 ms timeout — the newmodel/row_idsargs ride the existing subject.
services/prompt-runner/
New
src/request.ts— the purebuildRequest(filledPrompt, { model, tools, max_tokens })function. The single request-body assembler.New
src/preview.ts— theprompt.preview.requestedhandler: fetch prompt, fetch the one row,fillTemplate,buildRequest(with the passedmodel/max_tokens), return{ filled_prompt, request_body, bind, unbound_tokens }. No send.src/anthropic.ts—runPromptacceptsmodel, delegates body assembly tobuildRequest.src/run.ts— acceptmodel,max_tokens, androw_ids; threadmodelandmax_tokensintorunPrompt; whenrow_idsis present, select those rows instead of slicing byrow_limit.src/server.ts— subscribe toprompt.preview.requestedand dispatch it to the preview handler; theprompt.run.requestedpayload type gains optionalmodel,max_tokens, androw_ids.
packages/workspace
New
src/models.ts— the model registry (id,label,note) plusDEFAULT_MODEL/DEFAULT_MAX_TOKENS, and theModelId/ModelEntrytypes. Consumed by the request-reviewer toggle; re-exported from the package index.
packages/workspace/src/types.ts
TokenBindingtype — one{{token}}, its resolved value, a bound flag.PreviewResulttype —{ filled_prompt: string; request_body: unknown; bind: TokenBinding[]; unbound_tokens: string[] }.ResponseRecord/ResponseFlagtypes (shared with the response-reviewer spec).prompt.run/prompt.previewarg types gain optionalmodelandmax_tokens;prompt.runalso gains optionalrow_ids.PromptTemplate—default_model?is noted as a future field, not added now.
Federation wiring
request-reviewer is a federated remote built exactly like
prompt-template-manager (2026 05 21 03 Shell Federation Three Lessons):
expose a mount function, no shared block, ship CSS as a side-effect
import.
apps/request-reviewer/gets:rsbuild.config.ts(name: 'requestReviewer',exposes: { './mount': './src/mount.ts' },port: 3004,dev.assetPrefix: 'http://localhost:3004'),package.json,tsconfig.json, andsrc/{mount.ts, index.ts, App.svelte, app.css}.Port 3004 — record-collector is 3002, prompt-template-manager 3003; 3004 is next. (workspace service 3001, shell 3100.)
shell/rsbuild.config.ts— addrequestReviewer: 'requestReviewer@http://localhost:3004/remoteEntry.js'to theremotesmap.shell/src/remotes.ts— add aREMOTESentry (id: 'requestReviewer',label: 'Request Reviewer',importMount: () => import('requestReviewer/mount')); optionally aPAIRINGSentry for the prompt-template-manager pairing.
Build phases
Phase 1 — the service preview path (no UI)
buildRequest() extraction, prompt.preview capability + handler, the
the packages/workspace model registry, and model / max_tokens /
row_ids on prompt.run.
Verifiable by invoking prompt.preview directly over the workspace
WebSocket and asserting the returned request_body matches what prompt.run
would send. This phase de-risks the whole feature — once the preview
equals the fire, the UI is decoration.
Phase 2 — the remote scaffold + federation wiring
apps/request-reviewer/ rsbuild config, mount.ts, index.ts, a minimal
App.svelte that connects to the workspace singleton and renders a
placeholder. Wire it into the shell (rsbuild.config.ts + remotes.ts).
The walking skeleton of the remote — it mounts in the shell and connects.
Phase 3 — the review UI
Prompt + record-set pickers, the row stepper, the resolved-prompt view with
substitution marking, the model toggle, the JSON-request toggle, the token-
binding panel. Driven by prompt.preview. No firing yet.
Phase 4 — fire actions + integration
"Fire whole set" and "Fire this row" wired to prompt.run (with model,
max_tokens, and — for the single row — row_ids). The
augment-it:review-request handoff event listener. The co-existence
PAIRINGS entry. A theme-token audit across all three modes.
Firing exits to the response-reviewer; that remote and its response-store
are specified in the companion document
Response Reviewer and Response Store and built in the same body of work.
Resolved decisions
The three open questions from the first draft are now settled:
toolsselection stays prompt-only.web_searchis set in prompt-template-manager; request-reviewer shows it in the JSON view read-only and never toggles it per-run. The stored prompt and the fired request cannot diverge.max_tokensis a free numeric field, default 4096 — see §Max tokens.Firing exits to response-reviewer, which is built alongside request-reviewer rather than deferred. The pre-flight and post-flight surfaces ship as a pair — see Response Reviewer and Response Store.
Still open
Multi-provider (OpenAI and beyond). This spec is Anthropic-only —
anthropic.tsis the single LLM caller and the model registry holds only Claude models. Supporting other providers is a larger decision (a provider abstraction, a second key, divergenttoolsschemas) and belongs in its own exploration, not here.
See also
Response Reviewer and Response Store — the companion spec; the post-flight surface request-reviewer's fire exits into, built alongside.
Why Response Reviewer and Highlight Collector Exist — the blueprint rationale for the post-flight stages.
Tanuj Request Reviewer As Built — the prior art; template-with-values worked, and its copy-pasted-logic smoking gun is why preview is a service capability, not a frontend reimplementation.
Original and Enhanced Record Instances — the record-instance model;
row_idsonprompt.runis the input seam the fold assumes.Build the Shell Tiling and Peek Deck — the enrich control and the window-event handoff pattern request-reviewer reuses.
Augment It as CRM Augmentation Pipeline — the full pipeline this stage sits inside.