Proposal-Only Workflow Pattern¶
Purpose¶
A proposal-only workflow (archetype: proposal_only) is a human-in-the-loop workflow that produces a structured plan, review brief, or proposal artifact for operator review. The workflow itself takes no irreversible action. All phases or steps in the output artifact are status=proposed or status=blocked — they are never executed automatically.
This pattern separates planning from execution. The workflow produces a proposal; a human reviews it; a downstream execution or approval workflow (if needed) performs the actual actions.
When to Use¶
Use proposal_only when the workflow's purpose is to plan, assess, or recommend rather than to execute:
- Compliance review and gap assessment
- Migration planning (data, infrastructure, DNS, registrar)
- Deployment impact assessment and remediation planning
- Data cleanup or deduplication proposals
- Content moderation review briefs
- Change proposal reviews (schema, API, config)
- Any high-stakes operation that requires explicit operator sign-off
Do not use proposal_only when the workflow is expected to write data or call external write APIs during the session. Use the standard archetype instead.
AppGenerator Routing¶
When AppPlanAgent plans a capability pack with surface_kind: workflow whose purpose is planning or assessment, the [WORKFLOW ARCHETYPE CONTEXT] injected section provides guidance to select proposal_only. The archetype specifies:
- Orchestrator defaults (including
human_in_the_loop: true) - The canonical agent sequence
- Tool constraints
- Required structured output fields
- Output invariants that the OutputAgent must enforce
Orchestrator Shape¶
workflow_name: ContentReviewWorkflow
max_turns: 30
human_in_the_loop: true # Always true for proposal_only
workflow_startup_mode: AgentDriven
orchestration_pattern: DefaultPattern
initial_agent: IntakeAgent
Rule: human_in_the_loop: true is required and non-negotiable for proposal-only workflows.
Canonical Agent Sequence¶
IntakeAgent¶
- Gathers operator scope: subject, tenant, domain, parameters.
- Confirms scope with the operator.
- Emits
NEXTon confirmation. - Completely read-only. Does not call module write actions or provider APIs.
structured_outputs_required: false
PlannerAgent¶
- Calls read-only tools:
list_*,get_*,read_*. - Synthesizes findings, recommendations, phases, risks, and assumptions.
- Emits
NEXTwhen the analysis is ready for serialization. - Must not call
execute_action,approve,create_*,update_*, ordelete_*. structured_outputs_required: false
ReviewerAgent (optional)¶
- Second-opinion agent for stakeholder review.
- May inject additional risks or caveats.
- Must not approve or execute anything.
structured_outputs_required: false
OutputAgent¶
- Serializes the proposal into the top-level structured output model.
- Must enforce all output invariants — regardless of what prior agents produced.
- Outputs JSON only (no prose).
structured_outputs_required: true- Has a
save_*tool withauto_tool_call: trueto persist the artifact.
Tools¶
tools:
# ── Read-only tools ────────────────────────────────────────────────────────
- agent: PlannerAgent
file: tools/list_items.py
function: list_items
description: >
Lists relevant records. Read-only. Does NOT create, update, or delete.
tool_type: Agent_Tool
auto_tool_call: false
- agent: PlannerAgent
file: tools/get_snapshot.py
function: get_snapshot
description: >
Fetches a data snapshot. Read-only. Does NOT mutate the snapshot.
tool_type: Agent_Tool
auto_tool_call: false
# ── Proposal artifact save tool ────────────────────────────────────────────
- agent: OutputAgent
file: tools/save_proposal.py
function: save_proposal
description: >
Persists the proposal artifact to context and emits a UI artifact.
Does NOT execute any phase. human_review_required enforced as true.
tool_type: Agent_Tool
auto_tool_call: true # auto-called after OutputAgent speaks
lifecycle_tools: []
Tool rules: - All tool functions must start with list_, get_, read_, or save_. - execute_action must not appear as a callable tool. - The save_* tool on OutputAgent must have auto_tool_call: true. - No write-path tools (create_*, update_*, delete_*) may be callable in the workflow.
Structured Output¶
Minimum required fields¶
models:
ChangeProposalBrief:
type: model
description: >
PLAN ONLY. No action is executed by this workflow.
human_review_required is always true.
fields:
proposal_id:
type: str
description: Unique identifier for this proposal (uuid4 recommended).
artifact_type:
type: str
description: >
Semantic type of this proposal
(e.g. migration_plan, compliance_brief, deployment_assessment).
human_review_required:
type: bool
description: >
Always true. This workflow takes no irreversible action.
Operator review is required before any change is made.
status:
type: str
description: >
Always "proposed" in workflow output.
Never "approved", "active", "in_progress", or "executed".
risks:
type: list
items: str
description: Identified risks or open questions for operator validation.
assumptions:
type: list
items: str
description: Key assumptions the proposal is based on.
next_step:
type: NextStepRef
description: >
Operator instructions for advancing the proposal after review.
next_step.requires_human_approval is always true.
Output invariants¶
The following invariants must be enforced by OutputAgent through explicit prompt instructions. They are not currently enforced at the schema level (schema-level invariant metadata is a future enhancement — see TODO below).
| Field | Invariant |
|---|---|
human_review_required | Always true |
status (top-level and all phases/steps) | proposed or blocked only |
next_step.requires_human_approval | Always true |
| Tool functions | Must start with list_, get_, read_, or save_ |
execute_action | Never callable in this workflow |
OutputAgent [OUTPUT FORMAT] section must explicitly state all invariants:
- id: output_format
heading: "[OUTPUT FORMAT]"
content: |
Output ONLY valid JSON matching the ChangeProposalBrief schema. No prose.
Critical invariants — enforce these regardless of prior agent output:
- human_review_required: true (always — never false)
- next_step.requires_human_approval: true (always)
- All status values must be "proposed" or "blocked".
Never "approved", "active", "in_progress", or "executed".
- No provider secrets or credentials in the output.
- No instructions to execute phases automatically.
TODO (future enhancement): Add schema-level invariant metadata to
structured_outputs.yamlso the runtime can validate invariants without relying solely on agent prompt text. Proposed shape:
Blocked/Deferred Phase Pattern¶
When a proposal includes phases that cannot be executed in the current product phase (write adapters not yet implemented, credentials not yet available, dependency not yet released), those phases are represented as blocked — not omitted.
Why blocked phases must appear in output¶
Operators benefit from seeing blocked phases so they understand what the workflow cannot do yet. A phase that is hidden is a phase the operator cannot plan around.
Blocked phase fields¶
phases:
- phase_id: dangerous_cutover
phase_type: nameserver_cutover
status: blocked # never proposed/approved/executed
blocked_reason: WRITE_NOT_IMPLEMENTED
deferred_to: Phase 3D # phase or condition when this becomes executable
risk_level: high
rollback_note: >
Revert nameservers to previous values via registrar web console.
DNS propagation takes up to 48 hours.
Rule¶
A tool call returning WRITE_NOT_IMPLEMENTED or a similar error code is a signal to mark the related phase as blocked — not to remove it from the plan.
Blocked phase structured output fields¶
Add these fields to phase/step models in proposal_only workflows:
MigrationPhase:
type: model
fields:
status:
type: str
description: >
Always "proposed" (planned) or "blocked" (not yet executable).
Never "approved", "in_progress", or "executed".
blocked_reason:
type: optional_str
description: >
If status="blocked": error code or reason (e.g. WRITE_NOT_IMPLEMENTED).
Null when status="proposed".
deferred_to:
type: optional_str
description: >
Phase or condition when this will become executable (e.g. "Phase 3D").
Null when status="proposed".
Handoffs¶
handoff_rules:
# Intake → Planner on scope confirmation
- source_agent: IntakeAgent
target_agent: PlannerAgent
handoff_type: condition
condition_type: expression
condition: ${last_message_text} == 'NEXT'
transition_target: AgentTarget
# Intake waits for operator between turns
- source_agent: IntakeAgent
target_agent: user
handoff_type: after_work
transition_target: RevertToUserTarget
# Planner → Output when proposal is ready
- source_agent: PlannerAgent
target_agent: OutputAgent
handoff_type: condition
condition_type: expression
condition: ${last_message_text} == 'NEXT'
transition_target: AgentTarget
# Output → user to present the proposal
- source_agent: OutputAgent
target_agent: user
handoff_type: after_work
transition_target: RevertToUserTarget
# Operator terminates session
- source_agent: user
target_agent: terminate
handoff_type: condition
condition_type: string_llm
condition: >
When the operator confirms they have reviewed the proposal and
are done with this session.
transition_target: TerminateTarget
Context Variables¶
definitions:
# ── Intake inputs ───────────────────────────────────────────────────────────
tenant_id:
type: string
description: Tenant scope for this proposal.
source:
type: state
default: null
intake_confirmed:
type: boolean
description: True once the operator confirms the review scope.
source:
type: state
default: false
triggers:
- type: agent_text
agent: IntakeAgent
ui_hidden: true
match:
equals: NEXT
# ── Analysis outputs ─────────────────────────────────────────────────────
proposal_artifact:
type: object
description: The finalized proposal artifact. Set by the save_* tool.
source:
type: state
default: null
What Proposal-Only Workflows Must NOT Do¶
- Change data, infrastructure, or external system state.
- Call
execute_actionorapproveas workflow actions. - Set any phase/step status to
approved,active,in_progress, orexecuted. - Set
human_review_requiredtofalse. - Call provider write APIs (DNS, registrar, storage, external SaaS).
- Omit blocked phases from the output artifact.
- Proceed to a next step without instructing the operator.
Future Activation Path¶
When blocked phases become executable (write adapters implemented, credentials issued, dependency released):
- The downstream execution or approval workflow gains access to the write-path actions.
- The operator launches that workflow with the approved proposal's phase list.
- Per-phase/step approval gates advance each item through the approval state machine.
- After approval: the execution workflow calls the write-path actions.
- Post-execution: an assurance or validation pass confirms success.
- Decommission or cleanup actions (if any) run as a final phase.
Relationship to Other Patterns¶
| Pattern | Purpose | Executes actions? |
|---|---|---|
proposal_only | Plan, assess, recommend | No — output is a proposal |
standard | Execute tasks with HITL scope gate | Yes — after scope confirmation |
| Quality gate (AppGenerator) | Validate generated artifacts | No — sets passed/blocked status |
| Mid-flight journey (MFJ) | Fan-out parallel child workflows | Depends on child archetype |
Checklist for New Proposal-Only Workflows¶
- [ ]
orchestrator.yamlhashuman_in_the_loop: true - [ ] All callable tool functions start with
list_,get_,read_, orsave_ - [ ] No
execute_actiontool declared - [ ] OutputAgent has
structured_outputs_required: true - [ ] Top-level output model has
human_review_required: boolfield - [ ] OutputAgent
[OUTPUT FORMAT]section explicitly states all invariants - [ ] Blocked phases have
status,blocked_reason, anddeferred_tofields - [ ]
next_stepfield guides operator to the downstream execution workflow - [ ]
README.mdstates "PLAN ONLY" and "no action is executed by this workflow" - [ ] No provider secrets or credentials in any workflow YAML file