Skip to content

Workflow Routing Transitions

The global workflow routing layer lives in factory_app/workflows/extended_orchestration/extension_registry.json. App/workspace roots may overlay it with their own workflows/extended_orchestration/extension_registry.json.

It is not agent routing, not app navigation, and not workflow-local task batching.

Overlay Contract

App/product workspaces that need hosted or operator-specific workflow entries without copying the shared factory registry declare:

{
  "extends": "mozaiks.default_workflow_registry"
}

The OSS loader then builds one effective registry:

  • workflows[], entrypoints[], workflow_sequences[], and transitions[] merge by id.
  • { "id": "...", "remove": true } removes an inherited item.
  • artifact_dependency_graph merges by family key.
  • App-local workflow folders override matching inherited workflow folders.
  • Referenced inherited factory workflows resolve from packaged factory_app/workflows/.

Do not copy default factory workflow registry entries into an app repo just to make hosted routes work.

Authenticated Launches

Shell entrypoints, in-chat transitions, and refinement requests use the shared API adapter's authFetch with the current ChatUI auth adapter. Supplying app_id and user_id in a launch payload does not authenticate the request; the runtime validates the bearer token and checks that the requested scope matches the authenticated principal. Local no-auth operation remains governed by runtime configuration, not by a browser-side bypass.

Starting a Factory app from Studio

The Factory app-type selector registers a draft through POST /api/studio/apps before resolving the chosen journey. Its third onResolve argument carries {build_registry_id} as launch metadata. The shell retains that selector across chained transitions and retries and sends it at the top level of POST /api/transitions/resolve, outside workflow context.

The existing chat_session_fields hook has a read-only route phase to resolve the owned target before selecting a target-scoped router. It does not allocate a build or an active chat. The prepare phase establishes the build binding when the actual workflow and chat are known. Missing or foreign targets fail closed. Retrying a failed transition keeps the draft and user choices; it does not register another app or relax target ownership.

Failed Workflow Retry

Studio's existing POST /api/workflows/trigger accepts retry_failed: true with trigger_source: "manual", the execution app_id, workflow_id, and source_chat_id. The source must be that principal's failed session (status 2) for the same host and workflow, with a current registered build binding. The optional build_registry_id is a selector, not target authority. Retry rejects context, trigger payload, journey, action, and artifact-key overrides. Ordinary source-chat launches without this flag retain their existing behavior.

Reopening a failed chat uses the existing authenticated chat metadata route. Its optional failure_message is the nonblank string named by the loaded workflow's validated failure_message_key, read from the same scoped session snapshot as status 2. Missing workflow configuration, absent text, and other statuses return no failure detail. No context object or guessed feedback field is exposed. Studio shows this text in a collapsed details card only when the current transcript has no visible workflow_failure message; changing the session or receiving nonfailed metadata clears the fallback. This projection does not write session state or insert synthetic messages into model history.

A genesis retry starts with fresh execution state and preserves the saved coding_participation choice (autonomous or guided) through the existing launch-context authority check. Missing choices retain the workflow default; invalid persisted values fail before launch. Other failed-run state is not copied. Approved concept, design, theme and subscription inputs continue to resolve through the workflow's existing artifact references.

A refinement retry uses the saved typed change request, selected baseline record, and journey through TriggerRoutingContribution; it does not reclassify the request or allocate a new build. Missing, foreign, retired, or inconsistent baseline records fail closed. Rebuild/full-restart intent is unsupported by this retry path and is rejected rather than converted to a partial revision. The trusted contribution's require_exact_route constraint runs after dependency checks but before router state persistence or chat creation. Unmet dependencies or workflow/journey drift reject the retry; dependency checks are not bypassed.

AppGenerator retains its declared InterviewAgent entry and revision prompt. Its existing first before_chat hook verifies the committed baseline archive and hydrates generated_files; it does not read a mutable workspace fallback. Lifecycle hook errors are logged, and assembly repeats the verification and rejects missing, changed, retired, or unsupported content. Archive integrity is verified by that loader, not by the trigger's record-scope checks.

Each retry gets a fresh chat with default execution state. Saved intent IDs and baseline/journey provenance survive a subsequent retry, while generated output, validation, counters, messages, and the failed source session are not copied or reset. An idle same-build chat does not itself supersede the current binding.

Contract

extension_registry.json has three concerns:

Concern Field Owner Purpose
Workflow registry workflows[] platform/workflow author Known workflows and hard dependencies
Workflow sequencing workflow_sequences[] runtime Auto-advance and transition checkpoints after workflow completion
Transition routing transitions[] platform shell author User choices, context seeds, silent redirects, confirmations

Workflow Launch Taxonomy

workflows[] also declares how each workflow participates in the user-facing conversation. This is first-party Mozaiks workflow metadata, not workflow-specific product logic.

Field Purpose
startup_mode Workflow start category read from the workflow's orchestrator.yaml; all existing orchestrator.yaml files are supported
interaction_mode Whether the workflow is user_guided, agent_directed, or backend_only
launch_behavior Whether an empty workflow chat should auto_start, wait_for_user, or never start in chat
handoff_style Whether the workflow should feel like continuous_chat, a separate_chat, or background work

startup_mode does not by itself decide whether the UI waits on a blank chat. For user-reachable workflows, the registry should normally use launch_behavior: "auto_start" so transition handoffs feel like the same conversation. UserDriven means the user participates in the workflow; it does not mean the user must type the first message after every transition.

For example, the brownfield intake path can route to the path selector first. Both the Add AI Workflows and Build App Features choices seed deterministic context such as brownfield_build_path and then continue into ExistingAppDiscovery, which uses that selected scope while it reads the app. The downstream build sequence can use the same context later when it hands off into the enhancement or generation phase.

Use this taxonomy instead of hardcoding workflow names in runtime code:

  • User-facing sequence workflows usually use interaction_mode: "user_guided" or "agent_directed", launch_behavior: "auto_start", and handoff_style: "continuous_chat".
  • Standalone manual workflow starts may use launch_behavior: "wait_for_user" and handoff_style: "separate_chat" when a blank chat is intentional.
  • Domain-event workflows use startup_mode: "BackendOnly", interaction_mode: "backend_only", launch_behavior: "none", and handoff_style: "background".

Workflow sequencing uses workflow_sequences[]. Treat this data as workflow sequence metadata, not shell navigation or workflow-local task batching. If a sequence includes an entry transition as its first step, entrypoints[] points to that transition for actual route entry.

Workflow-local task batching lives in:

<workflow>/extended_orchestration/task_batches.yaml

Do not put task batch graphs in the global registry.

Hard Rules

  • For shared build transition visuals, place transition components in factory_app/workflows/extended_orchestration/ui/ and export them from ui/index.js.
  • Keep transition UI files focused on transition screens only; keep shared runtime helpers in chat-ui/src/platform.
  • Do not put top-level component, config, title, background, option label, or option description fields in product transition declarations.
  • Do not put entry_transition on workflow sequence declarations.
  • Do not put product-specific presets such as dogfood shortcuts in the global transition schema.
  • Transition context_variables may seed deterministic context only when the target workflow declares those keys in context_variables.yaml.
  • Use entrypoints[] entries with transition: <id> to enter a transition screen.
  • Transition entry routes should use meta.shellMode: "focused" and transition UI should use ui.shell_mode: "focused" unless the transition intentionally needs normal product chrome.

Transitions

Transitions are router decisions. They may render UI, but the declaration stays semantic and workflow-agnostic.

{
  "id": "app_type_selector",
  "transition_type": "user_choice_context",
  "ui": {
    "component": "AppTypeSelector",
    "mode": "screen",
    "shell_mode": "focused"
  },
  "options": [
    {
      "id": "greenfield_app",
      "route_to": "ValueEngine",
      "context_variables": { "app_type": "greenfield_app" }
    },
    {
      "id": "brownfield_app",
      "route_to": "ExistingAppDiscovery",
      "context_variables": { "app_type": "brownfield_app" }
    }
  ]
}

Supported target types:

  • route_to can point to another transition id.
  • route_to can point to a workflow id.
  • The loader stamps route_type internally after validation.
  • Transition UI emits option_id; it does not emit route_to.
  • All user choice transitions declare route_to on each option.
  • Use user_choice_context when the main point is deterministic context seeding, even if multiple options still route to the same next step.
  • Use user_choice_route when the main point is branch routing across different targets.
  • options[].context_variables is merged into the accumulated transition context before target workflow creation.
  • The workflow start path filters context against the target workflow's context_variables.yaml definitions.
  • Keep transition declarations semantic. Put branded copy/images/layout in the React component file referenced by ui.component, not in transition option routing fields.

The shell renders transition UI through TransitionScreen. ui.component is a registry key, not a file path. Built-in screens such as LauncherScreen and ConfirmScreen are available for generic cases. For product-specific visuals, create a workflow-local transition component and export it via:

factory_app/workflows/extended_orchestration/ui/index.js

The registry owns routing and context semantics (transition_type, single-route route_to, options[].route_to, and options[].context_variables).

chat_session Transition Type

chat_session launches a workflow in the current chat surface without mounting a blocking overlay. It is the correct type for any post-generation review or revision interaction where the user must be able to type freely in the chat.

Declaration:

{
  "id": "app_review",
  "transition_type": "chat_session",
  "route_to": "AppReview"
}

Rules:

  • route_to is required and must point to a registered workflow id.
  • chat_session transitions must NOT declare a ui block — they never render an overlay component.
  • confirm_route, cancel_route, options, context_key, and routes are not valid on chat_session transitions.

Runtime behavior:

  1. The sequence auto-advances to the chat_session transition after the preceding workflow completes.
  2. TransitionScreen receives the transition, detects chat_session, and immediately calls onResolve(null) without mounting any UI.
  3. The shell calls /api/transitions/resolve and receives resolution_type: "chat_session" with chat_id, workflow_id, and the filtered context_variables from the active journey.
  4. The shell switches the active chat session in-place to the launched workflow — no navigation away, no overlay, no blocked input.
  5. For a chat_session step, JourneyOrchestrator projects the completed source session's context into chat.transition_requested.context_variables using the declared route_to workflow. Only same-name target source.type: state inputs writable by transition_router are carried. Server-owned identity, undeclared fields, and the target's computed progress state are excluded. This is the same launch-input projection used for direct workflow steps; there is no all-context copy or inferred field mapping.
  6. The shell submits that projection with source_chat_id. The server resolves the owned build binding again and validates the target launch context. SecurityReadiness populates artifact_version_id from its verified current artifact even when the optional launch selector was omitted, so AppReview receives the assessment and its artifact identity together. The advisory summary does not authorize promotion or replace server artifact validation.

AppGenerator declares and publishes bundle_path and lifecycle_state after registering the staged app directory on the current build run. The path is the existing generated/apps/{app_id}/{build_id}/app location, not a new path inferred by the browser or SecurityReadiness. SecurityReadiness declares these same-name review inputs together with app_validation_status, app_validation_strategy_used, app_validation_preview_url, integration_tests_passed, and app_bundle_acceptance_status. These inputs are router-writable, not caller-writable. AppReview also explicitly permits the router to seed its closed validation fields. Evidence is carried unchanged; missing evidence remains missing and blocks the existing review checks rather than becoming a successful or skipped check. The review UI labels absent results Missing and reserves Skipped for an explicit skipped result.

A successful SecurityReadiness assessment with zero findings sets security_readiness_recorded=true without dispatching finding-row writes. persisted=false in that case is expected, not a permission or availability failure. Only an actual unsuccessful recording with persistence_error supports that failure message.

When to use:

Use chat_session when the interaction after transition must happen conversationally — the user can type revision requests, the agent can respond, and the flow can conclude through agent-authored completion signals rather than a button-driven confirm/cancel overlay.

The canonical example is app_review, which launches the AppReview workflow so the ReviewAgent can present a build summary artifact in chat and the user can type revision requests or click Promote without a blocking screen. Revision events emitted from this chat session must carry the reviewed artifact_key, artifact_version_id, source_surface=app_review, lifecycle_state, and staged bundle_path so the Refinement Engine routes against the reviewed bundle rather than an ambient app workspace. If the trigger response is harness_decision, the chat shell must feed it into the shared pending harness decision UI before launching any downstream workflow.

The App Workbench opens on the draft preview and change request. Code, raw diffs, validation commands, and export tools remain available through explicit controls. Refinement acceptance and activation remain separate, server-authorized actions. A genesis artifact can already be the current version; the user still reviews it and explicitly activates it. Delivery confirmation remains visible when the workflow requires it. When a registry run is in review and names a saved artifact, the app's primary Studio action opens its existing Build Review page, even if the earlier build conversation is still recorded. Opening that page loads the saved version; it does not start a workflow or allocate a preview. Drafts without a saved artifact retain their existing build conversation or Building destination. Build and integration warning details start collapsed, with their count visible; failed and skipped checks remain visible. Expanding integration warnings reveals the full list without changing the validation result or promotion gate. Review notes, including text-diff omissions, remain available separately from the always-visible validation blocker. A failed preview attempt stops status observation and keeps its original error until an explicit retry, stop, or version change. Preview errors are shown as collapsed plain-text details. The chat typing indicator follows current loading state: historical tool output cannot restart it after an interactive request or terminal event has stopped activity. Scoped refinement preserves the exported bundle's root .env.example, .env.staging.example, and .env.production.example templates. Actual environment files and nested credential paths remain blocked; the existing deployment acceptance checks still reject real secret values in those templates.

Dependencies

Dependencies are hard prerequisites and belong on workflow entries:

{
  "id": "AppGenerator",
  "dependencies": ["DesignDocs", "AgentGenerator"]
}

SessionRouter enforces these before workflow start. Dependencies are not cards, tabs, or transition options by default.

Workflow Sequences

workflow_sequences[] is the preferred field for ordered workflow auto-advance:

{
  "id": "build",
  "description": "Full build pipeline.",
  "affected_declarative_families": ["concept", "brand", "design_docs", "workflow_bundle", "app_bundle"],
  "steps": [
    { "transition": "app_type_selector" },
    { "workflows": ["ValueEngine"] },
    { "transition": "coding_journey_selector" },
    { "workflows": ["DesignDocs"] },
    { "workflows": ["AgentGenerator"] },
    { "workflows": ["AppGenerator"] }
  ]
}

Rules:

  • Use sequences only when completion of one phase should automatically start the next phase.
  • Use one workflow per step for serial execution.
  • Use multiple workflows in one step only when they should run as a parallel phase.
  • Use { "transition": "<id>" } when a completed workflow should pause on a surfaced checkpoint before the next workflow starts.
  • Use entrypoints[].sequence to declare the default journey entered by a route.
  • Use transitions[].options[].sequence when a user choice must switch from the route's default journey into a different authored sequence.
  • Use affected_declarative_families on sequences when the builder control plane needs to derive artifact invalidation impact from the selected route.
  • Do not place a workflow in the same or earlier step as one of its required dependencies.
  • If B depends on A, author the sequence as A in an earlier step and B in a later step.
  • Do not list the same workflow more than once in a sequence.
  • Entry UI belongs to extension_registry.json -> entrypoints[] via transition. The sequence may include the same transition as its first step so the journey contract remains fully ordered.

Brownfield Context Refresh

brownfield_discovery_refresh is the canonical sequence for resolving a Refinement Engine ContextRefreshPlan after stale brownfield context blocks a risky refinement. It reruns ExistingAppDiscovery only.

ContextRefreshPlan creation is planning-only. It does not start this sequence from stale-context policy evaluation, dry-run, or refinement routing. The sequence starts only when an operator or caller explicitly invokes launch_context_refresh_plan, which resolves the registered sequence and starts ExistingAppDiscovery through the normal workflow launch path.

This sequence is non-mutating by shape:

  • it contains no AppGenerator or AgentGenerator step
  • it creates no staged patch and no generated overlay
  • it does not promote or mutate source repositories
  • it refreshes app-context artifact families and registers a new AppContextVersion through ExistingAppDiscovery persistence
  • when source files are available, it persists source_context_bundle before app_context_graph so refinement tools can search and read exact code

It does not run AppGenerator, AgentGenerator, or DesignDocs.

After workflow completion, the Refinement Engine can call complete_context_refresh to compare the previous context version with the new current AppContextVersion and produce a ContextRefreshResult. That result records whether stale context was actually resolved. It does not relaunch workflows, mutate source repositories, or retry the blocked refinement automatically.

Brownfield generation/build sequences remain separate:

Sequence Purpose
brownfield_app_adoption Capture the app path choice first, then ask which repo to analyze, then run App Intelligence indexing and discovery chat with that scope already selected.
brownfield_overlay_generation User-facing Add AI Workflows path. Starts at ValueEngine, then generates approved app-aware AI workflows, chat surfaces, and required safe adapters while the existing app remains source of truth.
brownfield_module_generation User-facing Build App Features path. Starts at ValueEngine, then generates Mozaiks-owned modules, workflows, pages, data contracts, and extended functionality only for explicitly approved expansion scope.

The brownfield_path_selector screen is the first brownfield decision after the app-type selector. It should frame Add AI Workflows as the normal first move for most existing apps. Build App Features is for intentional module and product expansion, not an automatic full-repository rewrite. Both choices route into brownfield_repo_input, which asks which repo to analyze before ExistingAppDiscovery starts. The selected internal value (light_integration or full_migration) becomes downstream scope context.

Concept intake readiness

ValueEngine intake returns a strict ValueInterviewResult with agent_message and outcome: needs_input | ready. Its workflow-local record_value_interview auto tool validates the output before the existing outcome wrapper records interview_outcome and the bounded interview_attempts counter. Both have only deterministic tool writer authority. ready routes to research; needs_input returns to the user; invalid output or exhausted attempts fails the workflow. Conversation text never changes readiness. A sufficient user brief advances without another confirmation or implementation vocabulary in the chat.

Readiness starts concept research only. The separate structured concept review still requires owner approval before downstream generation. No sequence, entrypoint, or app authorization boundary changes with this intake contract.

Concept review request_changes requires nonblank feedback. The review tool persists it as concept_review_feedback and returns changes_requested, which routes directly back to GapAnalysisAgent without another chat message. The revised concept is a new draft with a fresh review ID and no approved scope until the owner approves that draft. Cancellation, stale or invalid responses, and the existing three-attempt review limit still fail closed.

After a draft-bound approval and its reviewed summary are persisted, ValueEngine updates the existing Studio app registry name through its service. The write is conditional on the review owner, execution host, target app, registry ID and current build ID. It changes only name metadata and timestamps, preserving app identity, lifecycle, build history and bindings. Manual/imported names retain precedence; stale or foreign bindings cannot rename a record or advance this approval tool. Cancel, changes requested and failed review persistence do not write a name. Registry list projections retain nullable names and name metadata so unnamed builds display a readable draft label until approval. Existing saved records are not renamed retroactively by this change.

DesignDocs uses its existing design_docs_save_feedback as the terminal failure explanation, so a rejected design reports the actionable reason in the workflow UI.

Autonomous stages with nothing to generate

Only exact coding_participation: autonomous suppresses the extra review for two validated empty decisions. SubscriptionContractDesigner records an explicit no-contract result as not_required, with user_confirmed: false, after its existing concept and contract guards. AgentGenerator records a validated empty workflow partition as not_required and uses its existing no_workflows route. Both must persist their actual disposition before completing. Nonempty plans, collaborative mode, and missing or unknown modes retain their existing reviews. App validation, final artifact review, acceptance, and promotion gates still apply.

DesignDocs defaults coding_participation to guided. Brownfield and refinement sequences that do not visit the participation selector therefore keep these reviews; an explicit upstream autonomous choice is preserved by context relay.

Discovery human continuation

ExistingAppDiscovery/transition_graph.yaml explicitly routes human replies back to the unfinished discovery stage using identity_complete, capabilities_complete, plan_complete, and decomposition_complete. The runtime's initial user-to-agent dispatch is bootstrap-only. It does not substitute for these workflow-local return edges, including after reconnect.

Selecting an adoption_level does not confirm the adoption plan. The planner remains at its human review boundary until the user confirms the recommendation and record_adoption_plan validates the workflow's typed AgentAugmentationPlan. The tool records the existing finite adoption choice and scope together with plan_complete; text alone cannot advance this stage. Embed/bridge then proceeds to assembly. Ecosystem/gradual modernization first reviews decomposition and records its typed ModuleDecompositionPlan through record_module_decomposition. Both agents acknowledge successful recording conversationally. Neither uses NEXT as a completion writer. Decomposition remains workflow-local evidence.

The recorded plan, adoption choice, and completion flags use existing deterministic tool writer authority. Automatic structured-output projection cannot replace the approved plan. Assembly rejects a conflicting adoption choice before persistence and uses the recorded plan for approved scope rather than accepting scope changes from the assembler's synthesis.

Assembly has no additional human review boundary. Its automatic save tool uses the canonical tool outcome contract: one deterministic attempt, saved only after all required artifact drafts persist and the final AppContextVersion registers successfully. That outcome terminates discovery as workflow_complete. Missing output, mapping/persistence errors, or a missing fresh tool result fail closed; preloaded version references and agent text cannot report completion. Failure leaves the prior current context intact and does not publish new save references. The overview refresh is best effort after durable registration. This completes discovery, without authorizing source changes, app bundle promotion, or a cross-workflow handoff.

Brownfield App Intelligence UX

The existing-app intake path has two user-facing context surfaces:

  1. ExistingAppDiscovery streams a persistent inline AppIntelligenceProgressCard from normal UI_Surface tool-call events. When indexing reaches ready, that card collapses to a compact completed marker so the chat does not compete with the artifact panel. After the before_chat preload completes and before the first agent message, it emits AppIntelligenceOverviewCard in the artifact panel. The card renders the prompt-safe product readout: a concise app summary and major feature areas translated out of code terminology. It does not render raw source contents, graph metrics, source refs, scanner codes, or AI opportunity lists. ValueEngine owns enhancement recommendations after the user chooses an enhancement path.
  2. The brownfield path now enters brownfield_path_selector directly after app_type_selector, then brownfield_repo_input, then ExistingAppDiscovery with the selected build path already set. The repo intake screen captures the source repo before discovery starts.

These surfaces are not AppPages. The progress card is inline workflow UI and AppIntelligenceOverviewCard is artifact workflow UI owned by ExistingAppDiscovery. The Refinement Engine consumes the same App Intelligence through checkpoint tools when the user later asks to revise or extend the app.

Existing-app discovery also materializes a local Studio app-registry record from the shared build lifecycle event. The build-events outbox still exists for hosted delivery, but /apps must not depend on that callback being configured: as soon as ExistingAppDiscovery starts, the imported repository appears as an in-progress app with a continue link back to the discovery chat.

Hosted delivery is at least once. The existing build-events outbox stores one immutable envelope per execution host, build, and event type. Re-emitting a lifecycle hook retains the original payload, timestamp, and idempotency key; it does not reset delivery attempts or retry scheduling. Receivers can therefore verify an exact replay without accepting changed data under the same key. A successful acknowledgement is terminal: a later failed delivery cannot make the event pending again. This is Factory callback durability, not AG2 agent scheduling or permission to promote or deploy an app.

Navigation Entry

The app shell enters a transition directly:

{
  "entrypoints": [
    {
      "id": "create_app",
      "path": "/create",
      "label": "Create App",
      "sequence": "build",
      "transition": "app_type_selector"
    }
  ]
}

Optional shell CTAs are declared separately in shell.json:

{
  "header": {
    "actions": [
      {
        "id": "create-app",
        "intent": "create_app",
        "label": "Create App",
        "path": "/create"
      }
    ]
  }
}

If the CTA needs to change during an active workflow session, use semantic shell action variants in shell.json; do not put route override logic in the workflow registry.

RouteRenderer mounts TransitionScreen, and transition resolution calls /api/transitions/resolve. If the result is another transition, the shell mounts that transition. If the result is a workflow, the backend creates a chat session and the shell navigates to chat.

The shell forwards entrypoints[].sequence as journey_id when it starts a transition or workflow route. If a transition option declares its own sequence, that option-level sequence overrides the inherited route journey for the selected branch.

Context Hydration

Transitions write deterministic key/value seeds into accumulated journey context. The runtime does not mutate workflow files. Before a workflow chat session is created, context is filtered against the target workflow's declared context_variables.yaml definitions. AG2 receives only declared keys, and agent visibility remains controlled by that workflow's agents: exposure map.

Generator Rules

AgentGenerator may generate extension_registry.json, but it should default to:

  • workflows[] with explicit dependencies
  • workflow_sequences[] only when auto-advance sequencing is required
  • transitions: [] only when the generated app has no journey decisions

AgentGenerator may generate transition React stubs under extended_orchestration/ui when transition visuals need product branding. Persistent app pages still belong to AppGenerator schemas. Agent UI tools belong to workflow-local ui/ plus Python tools. If AgentGenerator emits a transition, it must keep routing/context deterministic and bind ui.component to a registered transition component key. Product-specific copy/images/layout belong in the React stub.