What it enables
- Right-shaped output for every scenario. Per-job dispatch (commit
04147e9) made the panel polyglot in what it shows but not yet in how. This change closes that loop: each scenario’s terminal output renders in a layout fitting its shape, instead of the code-build-shaped default. - First-class
scenario_idon the wire. BothJobStatusResponseandPromptThreadStatusResponsenow exposescenario_idso any future per-scenario UI (timeline labels, filters, list badges) can switch on a typed enum rather than fishing insideaudit_log. - Backwards-compatible. Jobs created before the column existed fall back to a server-side audit-log extraction (so the UI still discriminates correctly), and unknown scenario_ids fall through to the existing Markdown renderer (no regression on ad-hoc / future scenarios).
What’s changed
Backend — Postgres + workerworker/worker/storage/postgres_repo.py: additive migration onensure_schema()—alter table job_runs add column if not exists scenario_id text. Thesave_resultupsert writes the new column on every persist.worker/worker/models.py:JobExecutionResult.scenario_id: str | None = None.worker/worker/orchestration/integration.py:IntegrationProcessor.save_resultaccepts and forwardsscenario_id.worker/worker/main.py:- Success path passes
scenario_id=run_result.scenario_id or None(the field already lived onScenarioResult). - Failure path falls back to
job.metadata.get("scenario_id")so failed jobs still record which scenario they were trying to run.
- Success path passes
mother-ai/src/models.rs:JobStatusResponseaddspub scenario_id: Option<String>.PromptThreadStatusResponseaddspub scenario_id: Option<String>(the FE renders per-prompt outputs, so the per-prompt scenario is the one that matters).
mother-ai/src/routes.rs:JobStatusFromPostgrescarriesscenario_id;fetch_job_from_postgresselects it and falls back via the newextract_scenario_id(audit_log)helper for jobs created before the column existed.fetch_prompts_from_postgresadds a correlated subquery for the latestjob_runs.scenario_idperprompt_id, falls back toprompt_threads.metadata.scenario_id(the request-time pin) when the joined value is null.
apps/frontend/lib/schemas.ts: newscenarioIdSchema = z.enum([...])+ScenarioIdtype. Added to bothjobStatusResponseSchemaandpromptThreadStatusResponseSchema.apps/frontend/components/workflow/panels/scenario-output-renderer.tsx(new): switches onscenarioId.quick_qaparses the JSON envelope (zodsafeParse) and renders an answer card + confidence pill + sources list, falling through to Markdown if the shape doesn’t match. Markdown scenarios (document_writing,research_brief,security_review,code_build) render via the existingreact-markdown+remark-gfmstack (no new deps).apps/frontend/components/workflow/editor/prompt-threads-list.tsx: the per-promptThreadResponseOutputnow readsscenarioIdoff the thread record and routes toScenarioOutputRenderer. Threads without ascenarioId(older runs, ad-hoc scenarios) keep the legacy Markdown pass-through — no regression.
Impact scope
- Additive across the stack. No existing callers break; the Rust struct fields are
Option<String>withskip_serializing_if = "Option::is_none", the FE zod schema isoptional(), and unknown scenarios fall through to the legacy Markdown renderer. A worker can persist new rows immediately after the migration runs (thealter tableis idempotent and runs on everyensure_schema()). - No new FE dependencies. Reuses the existing
react-markdown ^10.1.0+remark-gfm ^4.0.1already pinned fortask-output-modal.tsx,markdown-editor.tsx, and the sameprompt-threads-list.tsxreasoning rendering. - Production URL block needs no gating. The “LIVE” /
production_urlblock injob-detail-panel.tsxis conditional onproductionUrlbeing non-empty; non-code scenarios never set that metadata key, so the block already hides for them. - Backfill window. Jobs created before the column existed have
scenario_id = NULLinjob_runs; the Rust handler’sextract_scenario_idhelper recovers the value fromaudit_log.events[*].payload.scenario_id(where the orchestrator emits it) so the FE still discriminates correctly during the rollout window.
Tests
- Worker:
tests/test_tool_loop_runner.pyand the rest of the worker pytest suite still pass (592 tests; 9 pre-existing failures on master are unrelated). - Mother-ai:
cargo test --offline— 12 tests pass. - Frontend:
npx --no-install tsc --noEmitandpnpm run lint(biome) both clean. The FE has no test runner configured today, so the renderer is verified by type-check + manual UI smoke (fire one job per scenario from the composer and confirm the panel renders the right shape).
quick_qa→ answer card with a confidence pill (data-testid="confidence-pill") and source list.document_writing/research_brief/security_review→ Markdown renders (headings, lists, tables).code_build→ Markdown summary + (whenproduction_urlis in metadata) the LIVE block in the panel header.- A workflow whose threads have no
scenario_id(e.g. a job created before the rollout) → falls through to the existing Markdown pass-through.