Architect Flow: First‑Principles Design and Fixes
Summary
This document describes the first‑principles design for the Architect flow and the changes implemented to make it deterministic, policy‑driven, and robust. It covers the root cause, guiding principles, concrete changes in policies/built‑ins/CLI, test expectations, and troubleshooting tips.
Problem Statement
- The Architect CLI occasionally selected or emitted the wrong pipeline variant and did not consistently produce YAML.
- ConditionalStep behavior was modified in a way that broke its public contract (passing reshaped input to the condition callable), causing broad failures.
- The validity decision (valid/invalid) was not reliably tied to a single source of truth, leading to non‑deterministic branch selection.
Design Goals
- Deterministic loop behavior and branch selection.
- Single source of truth for validity:
context.yaml_is_validset by validation. - Strict policy contracts (no hidden reshaping of inputs or control‑flow swallowing).
- Clear, string final output for easy CLI extraction (no heuristics or re‑serialization).
First‑Principles Decisions
1) ConditionalStep Contract
- Call condition_callable(data, context) exactly as provided.
- Merge branch context back using ContextManager semantics.
2) Source of Truth for Validity
- Use context.yaml_is_valid only.
- Update the flag via a dedicated adapter validation_report_to_flag after validation.
- Select branches using a select_validity_branch function that prefers the last step’s {"yaml_is_valid": ...} output and otherwise falls back to context.yaml_is_valid.
3) Deterministic Loop Shape - Each iteration: - write/extract/store → validate → set validity flag → ValidityBranch. - If invalid: repair → apply repaired YAML → revalidate → reflag → record "valid" immediately via nested ValidityBranch. - Emit the current YAML as a string. - Exit the loop based on the validity flag (not counters).
4) Output Clarity
- End each iteration by emitting the current YAML as a string.
- End the top‑level pipeline with a final emit step so the overall StepResult.output is the YAML string.
Changes Implemented
- Policies
- Restored ConditionalStep contract (pass
(data, context)unchanged to the condition). -
Added a unit guard for this contract:
tests/unit/test_conditional_policy_contract.py.
-
Built‑ins
- Added
capture_yaml_textto normalize YAML outputs and update context. select_validity_branch: uses last output dict or falls back tocontext.yaml_is_valid.-
validate_yamlregistration resolves dynamically so test monkeypatches take effect. -
Programmatic Builder (
flujo/architect/builder.py) - The recipe has been superseded by a programmatic
StateMachineStepthat orchestrates the flow: GatheringContext → GoalClarification → Planning → PlanApproval → ParameterCollection → Generation → Validation (validate/repair loop) → DryRunOffer → Finalization. - The validation/repair loop is implemented via built‑ins (
validate_yaml,repair_yaml_ruamel) and setscontext.yaml_is_validas the single source of truth. -
A reference YAML still exists at
examples/architect_pipeline.yamlfor study and experimentation. -
CLI
- YAML extraction walks
step_historyand context attributes, preferring the most recent validyaml_text/generated_yamlrather than relying on heuristics. --debugprints step names and outputs to accelerate triage.- The state‑machine architect can be enabled via
FLUJO_ARCHITECT_STATE_MACHINE=1. Without the flag, the CLI uses a minimal builder that emits a conservative valid YAML scaffold.
Testing Impact
- Conditional tests are stable (contract guarded).
- Architect integration and GPT‑5 tests become deterministic:
- First iteration:
invalidrecorded (malformed inline steps). - After repair/revalidation: nested branch records
valid. - Final output is a YAML string; CLI extraction is straightforward.
Troubleshooting
- Enable CLI debug:
uv run flujo create --goal demo --non-interactive --output-dir /tmp/arch --debugto view step outputs. - Validate built‑in resolution:
validate_yamlis resolved dynamically; monkeypatch before the recipe is compiled. - If branch keys don’t show as expected:
- Confirm
check_validation_statussetsyaml_is_validprior toValidityBranch. - Confirm the nested
ValidityBranchexists after reflagging inside the invalid branch.
Rationale & Alignment
- All behavioral logic lives in policies/recipe/built‑ins; no executor core changes.
- Context updates happen via
updates_contextandContextManager. - Control‑flow exceptions are propagated (no swallowing).
- Deterministic outputs eliminate CLI guesswork.
Future Work
- Add a small optional env‑guarded trace in
select_validity_branchfor deep debugging (e.g.,FLUJO_DEBUG_COND=1). - Consider a minimal end‑to‑end test that asserts the presence of both "invalid" and "valid" keys using the nested ValidityBranch approach as a regression guard.
Framework Awareness of Custom Primitives (FSD‑025)
The Architect is aware of registered framework step primitives through the builtin skill flujo.builtins.get_framework_schema. It introspects the framework registry and returns JSON Schemas for each custom kind. This enables the Architect to generate valid YAML using new primitives such as StateMachine without hard‑coding their shapes.
Integration points:
- flujo.framework.registry: Central registration of Step kinds and execution policies
- flujo.builtins.get_framework_schema: Produces { "steps": { kind: json_schema } }
- YAML loader: Instantiates custom steps via model_validate() on the registered class
As you add new primitives to the registry, they automatically become available to the Architect.