Workflow Authoring Contracts¶
This document defines the canonical, strict YAML contracts for workflow bundles.
The runtime validates these files with Pydantic (extra="forbid"). Workflow bundles use the canonical YAML shapes documented here.
Required Files¶
At minimum, a workflow should include:
orchestrator.yamlagents.yamltransition_graph.yamlcontext_variables.yamlstructured_outputs.yamltools.yamlui_config.yaml
extended_orchestration/task_batches.yaml is optional and required only when the workflow uses workflow-local AG2 task batches.
middleware.yaml is optional and should be present only when the workflow uses prompt middleware.
a2a.yaml is optional.
Canonical Directory¶
workflows/{workflow_name}/
orchestrator.yaml
agents.yaml
transition_graph.yaml
context_variables.yaml
structured_outputs.yaml
tools.yaml
ui_config.yaml
middleware.yaml # prompt middleware config (only when needed)
extended_orchestration/
task_batches.yaml # task batch config (only when needed)
tools/
*.py
ui/
*.js
For builder/system workflows, the same contract applies under the shared generation-core workflow root. The file shape is canonical; the owning root depends on whether the workflow is app-owned or generation-core-owned.
There is no canonical generated-workflow workflows/_shared folder. Generated tools are owned by one workflow and live under that workflow's tools/ directory. If multiple generated workflows need the same capability, either generate explicit workflow-local tools for each workflow or promote the reusable behavior into a framework-owned mozaiksai.core.* API with a documented contract.
Factory-owned builder infrastructure is different: shared builder-only Python modules and reusable workflow React components may live under factory_app/workflows/_shared/ when multiple factory workflows consume them. Reusable workflow React components use the concrete factory_app/workflows/_shared/ui/ lane. Shared workflow UI remains explicit: each consuming factory workflow imports and re-exports/registers the shared component from its own ui/index.js. That path is for the factory repo itself, not for generated workflow bundle output.
Generation vs Refinement¶
Workflow authors must treat initial generation and post-generation refinement as separate authoring modes.
Initial generation workflows:
- create canonical state or the first canonical artifact set
- define ownership boundaries that later refinement can reuse
- stay focused on first-pass compilation, not universal revision handling
Refinement workflows:
- start from a persisted artifact version
- consume a classified change request
- operate on explicit scoped units
- widen scope only when the router decides the current unit boundary is insufficient
For builder workflows such as ValueEngine, DesignDocs, AgentGenerator, and AppGenerator, prompt design should stay clean:
- do not route delivered-bundle adjustments back through intake by default
- do not assume every change means "start over"
- do emit structured ownership metadata that later refinement can consume
AppGenerator's build_tasks with owned_paths, depends_on, and acceptance_criteria are the current canonical example of refinement-ready metadata.
Detailed refinement-routing plans are internal. The public authoring contract is that workflows should emit scoped ownership metadata so later refinement can choose the smallest valid re-entry point.
Revision-aware workflow context¶
Builder workflows should not rely on one boolean like refinement_mode as the long-term revision contract.
The target workflow input contract is:
build_mode—initialorrevisionrevision_scope—patch,design,feature, orcorechange_request_id— stable revision lineage idbuild_family— canonical name for the build record category (e.g.app_bundle);artifact_kindis also seeded for workflow compat during the naming transitionbuild_record_id— canonical name for the target build record ID;artifact_version_idis also seeded for workflow compat during the naming transitionrefinement_requestrefinement_request_metachange_intentimpact_setsequence_statusrevision_origin_workflow
Rules:
build_mode=revisionmeans the workflow starts from persisted builder state, not from a blank-slate assumption- a
corereroute intoValueEngineis stillbuild_mode=revision - workflows should anchor first on the revision request and persisted upstream summaries, then decide what to preserve or regenerate
- workflows should emit updated summaries, ownership metadata, and invalidation hints so later revisions stay structured
Canonical File Shapes¶
Every orchestrator.yaml must declare schema_version: mozaiks.orchestrator.v1; every structured_outputs.yaml must declare schema_version: mozaiks.structured_outputs.v1. These are required top-level fields. Missing, blank, or unsupported versions are rejected; the filename does not supply a default.
orchestrator.yaml¶
schema_version: mozaiks.orchestrator.v1
workflow_name: ExampleWorkflow
max_turns: 20
human_in_the_loop: true
workflow_startup_mode: AgentDriven
orchestration_pattern: Pipeline
initial_message: "Help the user define the requested workflow."
initial_agent: ExampleHostAgent
triggers:
- type: chat
description: Start from chat transport
Rules: - workflow_name must match directory name. - orchestration_pattern should be the selected AG2 Network patternbook label when known, or ag2_network for generic runtime workflows. It is metadata; routing still comes from transition_graph.yaml. - workflow_startup_mode must be one of: - AgentDriven - UserDriven - BackendOnly - initial_message is a hidden AG2/runtime seed. It is not user-facing transcript content and should not be used as visible copy. - failure_message_key is optional. It names a declared type: string context variable whose text the runtime reports to the user as the failed run's error when the transition graph ends the run as workflow_failed. Only a deterministic tool should write it, when it decides the run cannot continue.
agents.yaml¶
agents:
- name: ExampleHostAgent
prompt_sections:
- id: role
heading: "[ROLE]"
content: "You are a host."
max_consecutive_auto_reply: 5
structured_outputs_required: false
Rules: - Each agent needs name. - Each agent must provide either: - prompt_sections or prompt_sections_custom, or - system_message. - Auto-tool execution is derived from tools.yaml (agents with auto_tool_call: true tools); agents.yaml does not define a matching field.
Semantic Prompt Inputs¶
Apply the shared workflow prompt input rule described in Semantic Prompt Inputs. to first-party Factory workflows, generated workflows, prompt middleware, and task-batch initial_message content. A workflow may retain state or supply history; prompts must be correct for the inputs actually delivered to the current invocation.
When authoring a prompt:
- Describe the role as a responsibility, such as "produce a diagram of the proposed workflow pack." A roster identifier does not explain the task.
- Name each input's meaning and actual field or context key. Check the recipient's
context_variables.yamlagents.<Agent>.variables, prompt middleware, and task-batch context delivery before claiming it is available. A declared structured-output model or registry entry alone does not deliver an earlier result to a later invocation. - Explain each artifact's meaning and authority. Approved requirements constrain scope; evidence supports conclusions; a proposed plan remains subject to its review gate; validation feedback identifies contract failures to repair. Use the authority assigned by the actual workflow, not an assumption that all structured outputs are equally authoritative.
- Define how those inputs constrain or transform the reasoning and map into the required structured-output fields. Describe downstream requirements in terms of the artifact needed. "Read upstream outputs" is acceptable when these semantic dependencies are explicit; replacing a producer name with a field name alone does not explain them.
- Distinguish an allowed empty value from missing required evidence. Explain how declared authority resolves conflicting inputs and when clarification, repair, or failure is required. Do not invent prior dialogue or ask an unavailable agent for its result. If the workflow cannot supply a required input or handle its absence, repair the input contract before relying on it in prose.
For example, the Factory workflow-plan diagram consumes the projected workflows_spec as a proposed plan for user review. It preserves that plan's topology; concept_overview provides product context, not permission to add workflows. Its prompt can say:
Read the supplied upstream plan in
workflows_spec. It defines the proposed workflows and dependencies to present for review, not approved work to execute. InMermaidSequenceDiagramOutput, setMermaidSequenceDiagram.workflow_namefrompack_name; derivediagramparticipants and links fromnameanddepends_on, andlegendentries fromnameandpattern_name. Use the partition rationale fornoteswhen the plan spans multiple workflows. An emptyworkflows_specmeans no AI workflow is proposed; do not invent participants. Missing or conflicting required plan evidence is an input-contract failure; do not invent a plan or claim approval.
The plan validation and review tools own invalid, blocked, and changes-requested outcomes; prompts do not acquire authority to approve a plan or write protected routing state by mentioning those outcomes.
This identifies available data and its interpretation without requiring the model to locate a named participant's output. Model names and artifact field names remain valid vocabulary when their schemas or meanings are supplied. Likewise, generation instructions may require an exact initial_agent, registry, or transition destination value. Those are machine contract values, not directions to discover, remember, or contact a conversational participant.
transition_graph.yaml¶
transition_rules:
- source_agent: user
target_agent: ExampleHostAgent
transition_type: condition
condition_type: context_equals
condition_key: intake_complete
condition_value: false
transition_target: AgentTarget
- source_agent: ExampleHostAgent
target_agent: user
transition_type: after_turn
transition_target: RevertToUserTarget
Rules: - transition_type is after_turn or condition. - condition_type is context_equals, context_expression, or tool_called when transition_type: condition. - context_equals routes require condition_key and condition_value; they compile to a source-scoped AG2 ContextEquals condition. - context_expression routes require context_expression using Mozaiks ${context_variable} syntax over declared context references; they compile to a registered AG2 1.0 TransitionCondition. - tool_called routes require tool_name; they compile to a source-scoped AG2 ToolCalled condition. - Same-source condition rules must appear before fallback after_turn rules because AG2 evaluates lower priority first. - LLM intent classification belongs in the Refinement Engine before a workflow run is started or resumed. - The runtime compiles these rules into an AG2 1.0 TransitionGraph and resolves each turn through WorkflowAdapter. - AgentGenerator derives pattern-specific transition rules from factory_app/build_context/AgentGenerator/ag2_network_patterns.yaml.
context_variables.yaml¶
definitions:
host_complete:
type: boolean
description: True when host has enough input
source:
type: state
default: false
triggers:
- type: agent_text
agent: JokeHostAgent
ui_hidden: true
match:
equals: NEXT
- type: user_text
match:
contains: approved
example_topic:
type: string
source:
type: state
default: null
agents:
ExampleHostAgent:
variables:
- host_complete
- example_topic
Rules: - definitions must be a mapping (name -> definition), not a list. - agents must be a mapping (agent_name -> {variables: [...]}), not a list. - context_variables.yaml is the declaration layer for workflow state. At runtime, AG2 WorkflowState.context_vars is the live state used for routing. - The runtime supplies app_id, chat_id, user_id, and workflow_name even without workflow declarations. They remain runtime-only writable identity, not caller/tool-controlled state or replayable workflow data. - Agent prompts and structured outputs own semantic reasoning. Context variables declare the typed state and artifact values that reasoning produces. - Every agents.<Agent>.variables[] entry must reference a declared definitions key. - Every transition_graph.yaml condition_key and every ${...} reference in context_expression must reference a declared definitions key. - Every task-batch result.context_key and result.status_key, including conveyor-derived ${id}_results and ${id}_status, must be declared. - Valid trigger types for state variables: - agent_text - user_text - ui_response - Valid source types: - config - data_reference - data_entity - computed - state - external - file - build_context
structured_outputs.yaml¶
schema_version: mozaiks.structured_outputs.v1
registry:
JokeWriterAgent: JokeCollection
models:
JokeCollection:
type: model
fields:
jokes:
type: list
items: str
Rules: - models.<Name>.type must be model. - registry values must reference existing models keys. - Declared models are exact at runtime: an agent output carrying a field the model does not declare (top-level or nested) is rejected — never silently stripped — and no auto tool runs for that turn. Declare every field the agent may emit, or declare a deliberate open dict/optional_dict field when arbitrary keys are genuinely part of the contract. - Auto tools read the exact validated output through context_variables.get("structured_output") — a transient, read-only, runtime-owned projection. structured_output is RESERVED runtime vocabulary: declaring it in context_variables.yaml (as a definition or an agent view variable) is rejected at workflow load, no declaration metadata can claim it, and tools cannot write it; persist chosen data under your own declared context keys instead.
tools.yaml¶
tools:
- agent: JokeWriterAgent
file: save_jokes.py
function: save_jokes
tool_type: Agent_Tool
auto_tool_call: true
- agent: JokeCriticAgent
file: display_ratings.py
function: display_ratings
tool_type: UI_Surface
auto_tool_call: true
ui:
component: JokeRatingsCard
mode: inline
- agent: ReviewerAgent
file: request_revision.py
function: request_revision
tool_type: UI_Tool
auto_tool_call: false
ui:
component: RevisionRequestCard
mode: artifact
lifecycle_tools:
- trigger: after_chat
file: cleanup.py
function: finalize
Rules: - tools[].tool_type must be one of: - Agent_Tool - UI_Tool - UI_Surface - Agent_Tool is backend-only and must not declare ui. - UI_Tool is interactive and requires ui.component and ui.mode. - UI_Surface is one-way and requires ui.component and ui.mode. - ui_contract belongs only on UI_Tool. - Tool references use file and function. - Lifecycle tools are called with keyword arguments only, and two runners dispatch them. before_chat and after_chat (and on_fail raised inside orchestration) go through LifecycleToolManager, which passes context_variables when the signature declares it plus any of app_id, execution_id, chat_id, user_id, workflow_name, error the signature accepts. on_start, on_complete and the transport-level on_fail go through get_workflow_lifecycle_hooks from workflow_bridge, which passes app_id, execution_id, chat_id, user_id, workflow_name (plus message, details on failure) and never context_variables. Declare **_: Any and keyword-only parameters, and do not rely on context_variables in an on_start/on_complete tool. tests/test_registered_hooks_match_their_runners.py resolves every entry with both runners' resolvers and binds each runner's arguments.
Operation Outcomes¶
An outcome-dependent operation uses one structured-output agent and one auto-invoked tool. The optional tools[].outcome contract gives that operation a finite result vocabulary and an invocation budget:
tools:
- agent: CheckAgent
file: tools/check_document.py
function: check_document
tool_type: Agent_Tool
auto_tool_call: true
bind_to_agent: false
outcome:
context_key: document_outcome
attempts_key: document_attempts
result_field: status
values: [ready, needs_revision, blocked]
error_value: blocked
max_attempts: 2
retry_on: [needs_revision]
The function accepts context_variables and returns a mapping or Pydantic model whose top-level status field is one of these values. Custom code implements the operation but must not write its outcome or attempt keys. The runtime:
- writes
error_valuebefore execution so a prior success cannot survive; - checks and increments the operation's attempt count;
- invokes the tool once and validates its returned outcome;
- commits the result into context before AG2 evaluates the next transition.
Exceptions, invalid results, invalid counter state, and exhausted attempts map to error_value. Cancellation propagates and leaves the operation blocked. Only a prior retry_on result permits another invocation. max_attempts counts all invocations, including the first, across this workflow execution and its continuations; it is a static integer from 1 to 100. The default is one attempt with no retry outcomes. Use a new workflow execution for a new operation.
Both context keys must be declared with source.type: state, persisted: true, and writer_ids: [deterministic_tool]. The outcome key has type: string, authority_class: closed_writer_routing_state, and default error_value. The counter has type: integer, authority_class: closed_writer_quality_state, and default 0. Model output and user input cannot set these protected values.
Every declared value requires exactly one source-scoped context_equals rule on the outcome key. error_value routes to user, or to terminate with termination_reason: workflow_failed for an unattended workflow. The latter produces a failed run, not successful completion. An optional after_turn fallback uses one of these failure destinations; unrelated conditions from this source agent are rejected because they could bypass outcome handling. A missing current-turn result or a dynamic handoff/finish bypass fails execution before routing.
Application-specific outcomes and recovery agents remain extensible. A known recoverable result can route to a repair agent, an alternative operation, or a partial-result review. Unexpected errors pause for attention or end as failed. Side-effecting actions need application-owned idempotency or reconciliation; an invocation budget does not provide distributed exactly-once execution or rollback of external actions. AG2 provider-call retries are separate from business retries.
Validated structured outputs are dispatched at the packet boundary, before AG2 folds state and chooses the next agent. Auto-tool delivery uses the channel, agent, and incoming envelope's causation identity; collected output history is not dispatched again after the run. The runtime retains the existing auto-tool execution checkpoints for duplicate delivery within a process.
Noninteractive workflow channels fail after 300 seconds without progress, not after a fixed total duration. Each agent packet, checkpointed context write, or message posted to the channel restarts the idle deadline, so a retry that keeps advancing is not cut off by the time earlier turns used. A single turn that records nothing for 300 seconds fails the run with an error naming the idle budget and the last progress event. Workflows declaring interactive UI_Tool bindings have no idle deadline: user response waits remain unbounded, as required by the UI tool contract. Provider-call timeouts, graph turn limits, and operation attempt budgets bound every run.
These graph-bound operations belong to network agents, not task-batch triggers or workers. Task batches retain their existing failure_policy and retry_limit; put an outcome-dependent check in a separate network agent after the batch. Invalid combinations fail validation rather than bypassing the declared outcome routes.
Live Outcome Verification¶
With a local MongoDB instance and model credentials configured in the process, set RUN_LIVE_TOOL_OUTCOME_SMOKE=1 and run:
python -m pytest tests/test_workflow_tool_outcomes.py::test_live_materialized_outcomes_through_mozaiks_runtime -q -s --no-cov --reruns 0
This opt-in test spends real model tokens. It materializes a temporary workflow, loads it through Mozaiks, and executes real AG2 agents for success, repair, exception, unknown-result, and exhausted-budget cases. Assertions check persisted state, tool invocation counts, agent routes, failed-versus-completed status, and usage events. Only the tool faults are injected; model calls and persistence are not mocked. Generated fixtures and unique smoke run IDs do not modify active app bundles. This does not test external provider idempotency or a full app build.
RuntimeUIPrimitiveSmoke dogfoods outcome-controlled approval and artifact steps after a composer reply. With RUN_LIVE_AG2_SMOKE=1, tests/test_workflow_live_smoke.py::test_live_ui_primitive_smoke_workflow checks that each tool ran once and its persisted UI state matches the reported result. The live task-batch test in that file compares the synthesis output with actual executor metadata, rather than trusting the model's success claim. Continuations receive current values through the existing agent-declared context projection.
extended_orchestration/task_batches.yaml¶
Only add this file if the workflow uses workflow-local task batches. Task batches execute a typed list of work items with AG2 agents and merge results into one declared context key.
Minimal form:
version: 1
batches:
- id: document_reviews
trigger_agent: TriageAgent
source:
kind: context_variable
path: review_plan.tasks
task_model: DocumentReviewTask
worker:
mode: ag2_agent
agent_field: initial_agent
prompt_field: initial_message
context_fields:
- task_id
- owned_paths
- acceptance_criteria
execution:
concurrency: 4
dependency_field: depends_on
failure_policy: fail_batch
retry_limit: 1
result:
context_key: document_review_results
status_key: document_review_status
merge_strategy: collect_task_outputs
require_owned_paths: true
Rules: - version is 1. - batches[].id values must be unique within a workflow. - source.kind is context_variable or structured_output. - worker.mode is ag2_agent. - result.context_key and result.status_key must be explicit context variables that downstream agents understand. - Cross-workflow sequencing still belongs in extension_registry.json, not in task_batches.yaml.
ui_config.yaml¶
middleware.yaml¶
prompt_middleware:
- agent: JokeWriterAgent
filename: hook_inject_preferences.py
function: inject_preferences
Rules: - Prompt middleware declarations are compiled to AG2 1.0 middleware. - The runner calls each function positionally as fn(agent, messages) and ignores the return value; the hook changes the prompt only through agent.update_system_message(...). Name the first two parameters agent and messages. See Call Shape Contract. - Use lifecycle tools for side effects and structured outputs/runtime validators for output validation.
Guardrails¶
- Author YAML files directly; do not use
.jsondeclarative files for workflows. - Keep tool implementations in
tools/*.py; declaratives only reference them. - Do not create or reference global shared workflow tool folders such as
workflows/_sharedorapp.workflows._sharedin generated workflow bundles. - Factory-owned shared builder infrastructure may live under
factory_app/workflows/_shared/, but generated workflow packs must not emit or depend on that path. - Keep app-backend CRUD policy outside workflow declaratives.