Skip to content

Design prototype mode (ai-manager)

Status: Current | Last reviewed: 2026-09-24

A run mode of Agent Studio for designers: the real Next.js app with zero backend services. All /api/* calls, the collab WebSocket, Janus signalling, the Preview call's media-proxy offer, and the transcript/test SSE streams are intercepted in the browser by an MSW service worker; data comes from JSON fixtures captured off a live environment, and saves persist to localStorage. Because the prototype is the unmodified component tree, a designer's UI edits merge back as ordinary diffs.

Running

cd apps/ai-manager
npm install          # standalone — @trueserve/* deps are committed tarballs
npm run dev:prototype

dev:prototype sets NEXT_PUBLIC_PROTOTYPE_MODE=1 (plus NEXT_PUBLIC_ASYNC_AGENT_URL=/mock/async-agent for the workflow tester and the *_SWEEPER/TOOL_HEALTH_SCHEDULER=0 gates so the server boots without Postgres/Redis). The designer handoff doc — what works, what not to edit, how to hand changes back — is apps/ai-manager/prototype/README.md.

How it hangs together

Everything lives in apps/ai-manager/prototype/; exactly four real files carry a guarded touch point:

File Touch point
proxy.ts early NextResponse.next() when the flag is on (the session-cookie gate is the one server-side check MSW can't intercept)
app/layout.tsx <PrototypeGate> wraps AuthProvider — the worker must be intercepting before auth-context's mount-time GET /api/auth/session fires
next.config.js distDir: '.next-prototype' when the flag is on (Next 16 refuses two dev servers sharing one .next)
package.json the dev:prototype / prototype:capture scripts + msw devDependency

Mechanics worth knowing before touching it:

  • Store (prototype/store/): reads resolve localStorage-overlay → captured fixture (public/prototype-fixtures/, keyed by URL pathname); mutations write whole resolved bodies back to the overlay. The generic fallback handler answers unknown GETs from fixtures and logs [prototype] unhandled … — that warning loop is the coverage mechanism. Mock handlers must never return 401 (auth-context treats an own-API 401 as session expiry and redirects to /login).
  • Collab (prototype/collab/loopback-gateway.ts): MSW ws.link() intercepts /collab/ws/*; per room a browser-side Y.Doc is hydrated from the store via the app's own pipeline-shape.ts/workflow-shape.ts converters. Incoming updates wait for hydration before they are applied, so startup cannot overwrite the first edit. The room speaks the same y-protocols handshake as apps/collab-gateway/src/rooms.ts, and flushes to both /api/pipelines/{id} and /api/pipelines/{id}:draft overlays (the room keys on the base id, the page fetches the draft row). The autosave chip is fed through flush-registry.ts by the mocked /api/auth/collab-flush(-state) routes.
  • Phone Preview: /api/proxy/webrtc/offer is answered by a real RTCPeerConnection in the same page (silent oscillator track + the pre-negotiated video data channel id 0 — its onclose hangs the call up), so ICE/DTLS come up on loopback and the call UI behaves normally. Janus signalling is loopbacked to "registered" (prototype/phone/janus-loopback.ts) because the agent editor auto-registers SIP on mount and a dead server toasts on every open. Transcripts stream from prototype/phone/call-script.ts over mocked SSE.
  • Workflow tester: /test, the /mock/async-agent/sse/executions/… stream, and the authoritative test-trace poll are all generated from one per-execution schedule (prototype/workflow/execution-script.ts) so SSE animation and the trace poll agree; the trace run row must carry id (the panel renders run.id.slice(0,8)).

Distributing to a designer (no monorepo access)

The persistent designer source is the private Deloitte-US/agent-studio-design repository, embedded as the studio/design submodule. Designers can clone it independently. The framework pins its revision; shared UI changes are reviewed and imported separately into apps/ai-manager. See repository workflow for checkout, publishing, three-way sync and importing designer changes.

bash apps/ai-manager/prototype/scripts/export-repo.sh builds agent-studio-prototype-export.zip in the repository root: a standalone single-commit git repo holding apps/ai-manager + the shared/*.tgz tarball deps at the same paths, so her commits apply back to the monorepo without rewriting (git diff <seed-commit>..HEAD / format-patch → am). It exports the working tree (gitignore-filtered — the branch's interdependent WIP means bare HEAD may not build), strips the fixtures that name real people (users/groups/session/recents/favorites/notifications/conversations — the auth mock falls back to a built-in "Design Prototype" user), and prints a review list of email-like strings remaining in demo fixtures before you send it anywhere.

ZIP generation is for disposable snapshots. It refuses to overwrite a submodule, clone, dirty export or unrelated nonempty directory. --no-archive creates only the snapshot tree for the repository sync helper. Never export directly over studio/design.

The export incorporates the designer's root launcher: npm install runs npm ci --package-lock=true inside apps/ai-manager, and npm run dev, npm start or npm run prototype starts the mock app on port 3100. npm run dev -- --port 3101 forwards an alternate port. The root .npmrc disables an unnecessary root lockfile; the app's lockfile remains authoritative. Use npm run deps:update for intentional dependency updates. Use Node.js 22+ and npm 10.5.1+ to meet the current app dependencies' requirements.

prototype/export/package.json is the reusable launcher template. Export generation removes the copied app's predev monorepo hook and routes dev/dev:only to dev:prototype, preserving the current dependencies and prototype flags. These adjustments apply only to the exported copy; main-app startup remains unchanged. Root guidance and README describe the same commands. Import UI changes against the export's baseline, not by copying its package/startup files over the live application's versions.

Agent guidance in the export

The exporter installs prototype/designer-claude.md as the exported repository's root CLAUDE.md, with an AGENTS.md dispatcher for other coding agents. The source template has a different filename so it does not activate designer-specific rules in normal development. Framework guides defer to this policy inside studio/design; the copied app guide describes current production behavior without blocking design changes.

The designer directs visual and behavioral changes, including experiments that remove capabilities, simplify required inputs or need a new production contract. Current implementation invariants are reference material, not a veto. Agents implement the requested prototype and extend local mock APIs as needed; missing backend support does not require approval or a disabled control.

When preparing a commit, the coding agent updates docs/design-change-log.md inside the exported app with the actual change, potential production impact, engineering follow-up and verification. The file travels with the commit. Unknown impact, backend gaps and failing checks are recorded without blocking a design commit. There is no required waiver, engineering signoff or blocking hook. Complex contracts can link to docs/prototype-design-decisions.md; visual-only changes need a short note. The designer does not maintain a checklist.

The root guidance, dispatcher and app guide explain this scope consistently. Framework instructions defer to the prototype workflow inside studio/design; production imports retain the normal validation, documentation and release rules. Review shared UI, mock changes and impact notes together before production import. A mock remains a simulation even when the requested interaction works locally.

The export script resolves its source repository from its own location. Use the main apps/ai-manager exporter for new handoffs: the current app now includes the conversational preview behind FEATURE_CONVERSATIONAL_DESIGN (default off in regular deployments, enabled by dev:prototype). The older studio/real copy remains a historical design reference. See Studio rollout.

Shared conversational design UI

/studio and /studio/build/[briefId] always render LiveDesignHome and LiveDesignWorkspace. The output tabs, Thinking activity, composer and reference controls are shared with the real app. Do not add prototype-only page branches; mock an API contract instead so designer edits apply to the actual UI.

prototype/msw/handlers/studio.ts implements design list/create/read, NDJSON assistant turns and reference upload/download/delete. prototype/studio/ contains the local state, deterministic reply schedule, draft adapters and IndexedDB attachment store. Current sessions use the resettable tsf-proto:v1: overlay; files use the tsf-proto-design-references IndexedDB database. The reset badge clears both. Legacy StudioBrief storage is left untouched, but those IDs are not migrated or opened as current design sessions.

Creation is idempotent by creation key. Each turn records its request ID and schedule before streaming; polling or reloading projects progress from that saved schedule and completes the local draft once. Duplicate requests return 409 rather than replaying. References honor the current file-type, size and count limits; download uses a browser fetch so both the real API and MSW can serve the same control, with failures shown in the workspace.

prototype/studio/timing.ts defaults AI turns to 30,000 ms. Thinking updates arrive at roughly 0.6, 5.4, 10.8, 18 and 24 seconds (plus reference metadata at 14.4 seconds when present). Text streams from 64% to 94% of the schedule and completion occurs at its end. This is simulated UX timing, not measured production performance; the real application never uses these delays. NEXT_PUBLIC_PROTOTYPE_AI_LATENCY_MS overrides total mock duration, bounded to 1,000–120,000 ms, with non-positive/non-finite values falling back to 30,000. Restart the prototype after changing this client-side setting. Existing pending turns retain the schedule saved at acceptance.

The four types create actual local mock draft rows with matching base/draft aliases and version metadata. Voice/Chat use the app's stack builder, Translation uses its translation defaults, and Background starts with an inactive blank workflow. Example turns update descriptions and conversational prompts while preserving other editor settings. Overview data is sample presentation content: it does not compile specialists/procedures, interpret attachments, connect a system or run tests. Reference sharing is simulated and validation stays not_run. The prototype badge, sample replies and designer guidance explain these limitations without substituting a different screen.

The mock draft writer reconciles updates into any existing collab Y.Doc as well as stored rows. The gateway flushes pending edits and releases a document when its last client leaves. Reopening hydrates the current stored draft, so a later design turn cannot be overwritten by a cached editor document.

Run npm run test:prototype against a running export (default port 3100), or set E2E_BASE_URL for another port. The separate Playwright config needs no database or auth setup. It checks all four editor handoffs, streamed replies, reload, reference round-trips and limits, idempotent turns, reset and mobile panels. For repeated checks, launch the prototype with NEXT_PUBLIC_PROTOTYPE_AI_LATENCY_MS=1000. Also run the opt-in elapsed-time test against a default-timing server: E2E_REALISTIC_LATENCY=1 npm run test:prototype -- --grep '30-second'. No new production backend capability is required for this visual parity change; the live APIs already own these operations. See conversational design and the user guide.

Fixture capture

npm run prototype:capture -- --base-url <env> --login <user> <pass> (or --cookie "ai_manager_session=…"). Curated endpoint list + per-id expansion (agents list rows key on agent_id, and the script follows redirectToDraft → captures the {id}:draft rows the editor actually fetches). Scrubbing redacts secret-shaped keys and token-shaped substrings inside values (a custom_headers JSON string holding Bearer tsf_… was the live case), then hard-fails the run if any token pattern survives. Filenames replace : with ~ (Windows); index.json maps real pathnames to files.

See also