Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,10 @@ cython_debug/
.opencode/
.templates/
AGENTS.md

# smith managed
.flowr/
.opencode/
.templates/
AGENTS.md
# end smith managed
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@

All notable changes to this project will be documented in this file.

## [v0.5.0+20260505] - Fine Sift - 2026-05-05

### Added

- **Subflow exit resolution**: when a subflow exits, the exit name is resolved through the parent flow's transition map to determine the actual target state; previously the exit name was used directly as a state ID, causing sessions to land on invalid states
- **Subflow chaining**: after exiting a subflow, if the resolved target state has a `flow:` field, the stack is pushed again to enter the next subflow atomically (e.g., discovery-flow → exit → architecture-flow)
- **`session init` auto-enters subflow**: when the first state has a `flow:` field, `session init` pushes the stack and enters the subflow's initial state automatically
- **`next` shows all transitions**: the `next` command now displays ALL transitions including blocked/guarded ones, with trigger→target mapping and status markers (`[blocked]` + condition hints)
- **`next` JSON output uses `transitions` array**: replaces the old `next: [strings]` with `transitions: [{trigger, target, status, conditions}]` — a breaking change (pre-release)
- **`states --session`**: lists states in the current (sub)flow resolved from the session
- **`validate --session`**: validates the current (sub)flow resolved from the session
- **`check --session <trigger>`**: correctly shows transition conditions for the given trigger (previously the argument was silently captured by argparse as `flow_file`)
- **Session-aware dispatch refactor**: extracted `_dispatch_session_command()` to reduce cyclomatic complexity; unified session-aware routing for all commands
- **Post-mortem**: `PM_20260505_subflow-mechanism-non-functional` documenting the two critical bugs and testing gap

### Changed

- **`flowr/domain/loader.py`**: `resolve_subflows()` now tries the `flow` field path as-is first, then appends `.yaml` if not found — making the `.yaml` extension optional in flow definitions
- **`flowr/__main__.py`**: new helper functions `_find_flow_file()`, `_enter_subflow()`, `_resolve_subflow_exit()`, `_build_transition_list()`, `_format_transitions_text()`; `_apply_session_transition()` gains `flows_dir` parameter for parent flow resolution; `_cmd_next` and `_cmd_next_session` use rich transition output; argparse for `validate` and `states` now accept optional `flow_file` and `--session`
- **`flowr/cli/session_cmd.py`**: `cmd_session_init` auto-enters initial subflow when first state has `flow:` field
- **JSON is now default CLI output**: `--json` flag replaced with `--text` — JSON is the default for all commands; use `--text` for human-readable output
- **ADR_20260426_subflow_resolution** amended: `.yaml` extension now optional with fallback-append

### Fixed

- **Subflow path resolution**: `resolve_subflows()` failed for flow references without `.yaml` extension (e.g., `flow: discovery-flow`) — now tries as-is first, then appends `.yaml`
- **Subflow exit state resolution**: `pop_stack(target)` used exit name directly as state ID, producing invalid states — now resolves through parent transition map
- **`check --session <target>` argparse capture**: target argument was silently consumed as `flow_file` positional — now correctly routed as target trigger name
- **Test fixture gap**: added tests for flow references without `.yaml` extension and subflow exit resolution/chaining
- **Stack frame state bug**: `_enter_subflow()` recorded the pre-transition state instead of the subflow wrapper state in the stack frame, causing exit resolution to look up the wrong parent `next` map — now records the target (subflow wrapper) state

## [v0.4.0+20260502] - Refined Semolina - 2026-05-02

### Added
Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ flowr defines a YAML format for non-deterministic state machines with per-state
```
flowr validate deploy.yaml → valid: True
flowr states deploy.yaml → prepare, execute, review
flowr next deploy.yaml review → approve (guarded), reject
flowr next deploy.yaml review → approve → deployed [blocked] need: score=>=80
→ reject → failed
flowr transition deploy.yaml review approve --evidence score=85
→ from: review, to: deployed
→ from: review, to: deployed
flowr session init deploy-flow → session created at state: prepare
flowr --session transition approve → from: prepare, to: review
flowr mermaid deploy.yaml → stateDiagram-v2 ...
Expand All @@ -55,7 +56,7 @@ flowr mermaid deploy.yaml → stateDiagram-v2 ...

**Query.** States, transitions, conditions, attributes — ask any question the flow can answer.

**Sessions.** Init, show, set-state, transition, list. Subflow push/pop for nested workflows. One `--session` flag turns any command session-aware.
**Sessions.** Init, show, set-state, transition, list. Subflow push/pop for nested workflows. Auto-enters initial subflow on `session init`. One `--session` flag turns any command session-aware (including `validate` and `states`).

**Config.** `flowr config` shows where every value comes from — default, pyproject.toml, or CLI override.

Expand Down Expand Up @@ -111,8 +112,8 @@ review

$ flowr next deploy.yaml review
state: review
next: approve (guarded)
next: reject
approve → deployed [blocked] need: score=>=80
reject → failed

$ flowr transition deploy.yaml review approve --evidence score=85
from: review
Expand Down Expand Up @@ -152,15 +153,15 @@ default_session = default (default)
| `flowr validate <flow>` | Validate a flow definition |
| `flowr states <flow>` | List all state ids |
| `flowr check <flow> <state> [<target>]` | Show state details or transition conditions |
| `flowr next <flow> <state> [--evidence K=V]` | Show valid next transitions |
| `flowr next <flow> <state> [--evidence K=V]` | Show all transitions with trigger→target and condition status |
| `flowr transition <flow> <state> <trigger> [--evidence K=V]` | Compute next state |
| `flowr mermaid <flow>` | Export as Mermaid state diagram |
| `flowr session init <flow> [--name NAME]` | Create a new session at the flow's initial state |
| `flowr session init <flow> [--name NAME]` | Create a new session (auto-enters initial subflow) |
| `flowr session show [--name NAME] [--format FORMAT]` | Display current session state |
| `flowr session set-state <state> [--name NAME]` | Update the session's current state |
| `flowr session list [--format FORMAT]` | List all sessions |
| `flowr config [--json]` | Show resolved configuration with sources |
| `flowr --session <command>` | Run a command using session state |
| `flowr --session <command>` | Run a command using session state (works with validate, states, check, next, transition) |

`<flow>` accepts a file path or a short flow name (resolved from `.flowr/flows/`). Use `--flows-dir` to override the configured flows directory. All commands accept `--json` for machine-readable output. Evidence: `--evidence key=value` (repeatable) or `--evidence-json '{"key": "value"}'`.

Expand Down
20 changes: 15 additions & 5 deletions docs/adr/ADR_20260426_subflow_resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ When a state references a subflow via its `flow` field, the CLI needs to locate

## Decision

Subflow lookup resolves the `flow` field as a relative path from the root flow's directory, including file extension. Subflow entry is displayed as `<flow-name>/<first-state-id>`.
Subflow lookup resolves the `flow` field as a relative path from the root flow's directory. The resolver tries the path as-is first; if the file does not exist, it appends `.yaml` and tries again. This makes the `.yaml` extension optional — flow authors can write `flow: discovery-flow` or `flow: discovery-flow.yaml`. Subflow entry is displayed as `<flow-name>/<first-state-id>`.

**Amendment (2026-05-05):** The original decision required the `.yaml` extension explicitly. This was changed because real production flows omit the extension (e.g., `flow: discovery-flow`), and requiring it added friction. The fallback-append approach keeps explicit extensions working while also supporting bare names.

## Reason

Convention-over-configuration with path flexibility; matches project layout; supports subdirectories; no extra CLI flags needed.
Convention-over-configuration with path flexibility; matches project layout; supports subdirectories; no extra CLI flags needed. The extension-optional fallback reduces ceremony for the common case (same directory, `.yaml` files) while preserving support for explicit paths and subdirectories.

## Alternatives Considered

- **Explicit --flows-dir flag**: over-engineered for v1; adds cognitive load
- **Walk parent directories**: fragile; could find wrong files
- **Auto-append .yaml extension**: adds magic; explicit extension is simpler
- **Require .yaml extension always**: original decision; rejected after production flows showed authors prefer bare names
- **Subflow output as just first state**: loses context about which subflow you're entering
- **Subflow output as just flow name**: loses context about entry point

Expand All @@ -36,11 +38,19 @@ Convention-over-configuration with path flexibility; matches project layout; sup
- (+) Zero configuration; works with existing flow file layout
- (+) Supports subdirectories and relative paths
- (+) Clear, unambiguous output showing subflow name + entry state
- (-) Flow field must include `.yaml` extension — existing reference YAML files need updating
- (+) `.yaml` extension is optional — bare flow names work (e.g., `flow: discovery-flow`)
- (-) Flow field has dual meaning (lookup path vs canonical flow name from loaded YAML)

## Risk Assessment

| Risk | Probability | Impact | Mitigation | Accepted? |
|------|------------|--------|------------|-----------|
| Flow field has dual meaning (lookup path vs canonical flow name) | Low | Medium | Ambiguity is mitigated by convention; loader extracts canonical name from loaded YAML | Yes |
| Flow field has dual meaning (lookup path vs canonical flow name) | Low | Medium | Ambiguity is mitigated by convention; loader extracts canonical name from loaded YAML | Yes |
| Bare name matches wrong file in subdirectory | Low | Low | Resolution is relative to root flow's directory; convention keeps flows flat | Yes |

## Changes

| Date | Change | Reason |
|------|--------|--------|
| 2026-04-26 | Initial decision: require `.yaml` extension | Original ADR |
| 2026-05-05 | Made `.yaml` extension optional with fallback | Production flows use bare names; requiring extension was friction (PM_20260505_subflow-mechanism-non-functional) |
161 changes: 161 additions & 0 deletions docs/features/backlog/subflow-transition-overhaul.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
Feature: Subflow Transition Overhaul

The subflow mechanism (push/pop stack for nested flows) is completely
non-functional for real flows due to two critical bugs in path resolution
and exit handling. Additionally, the `next` command hides critical
navigation information — trigger names, guarded transitions, and evidence
requirements — making autonomous agent navigation impossible.

This feature fixes the subflow mechanism, enhances `next` to show the
complete navigation picture, and improves session command robustness.

Depends on: session-management (core), session-management-extended.

Status: ELICITING

Rules (Business):
- Flow references without `.yaml` extension are resolved by appending `.yaml` if the bare path doesn't exist
- Subflow exit resolves through the parent flow's transition map, not by using the exit name directly
- Subflow chaining: after exiting one subflow, if the resolved target enters another subflow, the stack is pushed again
- `session init` auto-enters the initial subflow if the first state has a `flow:` field
- `next` always shows ALL transitions including blocked/guarded ones, with status markers
- `next` output shows trigger→target mapping with inline condition requirements
- `next` JSON output uses `transitions` array of objects with `trigger`, `target`, `status`, `conditions`
- `check --session <target>` correctly shows transition conditions
- `states --session` and `validate --session` resolve the current flow from session

Constraints:
- Non-session commands remain backward compatible
- `next` JSON output is a breaking change: `next` array of strings replaced with `transitions` array of objects
- Domain layer (Session, SessionStackFrame) changes are minimal — fixes are in CLI/transition logic

## Questions

| ID | Question | Status | Answer / Assumption |
|----|----------|--------|---------------------|
| Q1 | Should `next` show all transitions or require `--all` flag? | Resolved | Always show all — agents need the full picture |
| Q2 | Should `next` JSON preserve backward compatibility? | Resolved | No — clean break, replace `next` array with `transitions` objects |
| Q3 | Should `set-state` support crossing flow boundaries? | Open | Phase 3 — may add `--flow` flag for recovery scenarios |

## Changes

| Session | Q-IDs | Change |
|---------|-------|--------|
| 2026-05-05 S1 | Q1-Q3 | Created: subflow resolution, exit handling, next overhaul, session robustness |

Rule: Subflow Path Resolution
As a flowr user
I want flow references without file extensions to resolve correctly
So that I can use clean flow names in YAML definitions

@id:sf-001
Example: Flow reference without .yaml extension resolves correctly
Given a flow YAML with `flow: discovery-flow` referencing a file `discovery-flow.yaml`
When the CLI resolves subflows
Then the subflow is loaded successfully

@id:sf-002
Example: Flow reference with .yaml extension still works
Given a flow YAML with `flow: child.yaml` referencing a file `child.yaml`
When the CLI resolves subflows
Then the subflow is loaded successfully

Rule: Subflow Exit Resolution
As a flowr user
I want subflow exits to correctly resolve through the parent flow's transitions
So that my session lands on the correct next state after completing a subflow

@id:sf-003
Example: Subflow exit resolves parent transition target
Given a session inside discovery-flow with stack frame pointing to main-flow/discovery
And discovery-flow exits with `complete`
And main-flow/discovery maps `complete → architecture`
When the user transitions with the exit trigger
Then the session pops the stack and lands at main-flow/architecture

@id:sf-004
Example: Subflow chaining enters next subflow after exit
Given a session exiting discovery-flow back to main-flow
And the resolved target `architecture` has `flow: architecture-flow`
When the subflow exit resolves to architecture
Then the session pushes a new stack frame and enters architecture-flow

@id:sf-005
Example: Subflow exit with invalid parent state produces error
Given a session inside a subflow with a corrupted stack frame
When the subflow exit cannot find the parent state
Then the CLI prints a clear error indicating the parent state is invalid

Rule: Session Init Enters Subflow
As a flowr user
I want session init to automatically enter the initial subflow
So that I can start working immediately without an extra transition step

@id:sf-006
Example: Session init auto-enters subflow when initial state has flow field
Given a flow whose initial state has `flow: discovery-flow`
When the user runs flowr session init main-flow
Then the session is created inside discovery-flow with a stack frame pointing to main-flow

@id:sf-007
Example: Session init without subflow works as before
Given a flow whose initial state has no `flow:` field
When the user runs flowr session init simple-flow
Then the session is created at the initial state with no stack

Rule: Next Shows Full Transition Map
As a flowr user
I want to see all transitions with trigger names, targets, and condition requirements
So that I can navigate without reading raw YAML files

@id:sf-008
Example: Next shows trigger→target mapping for all transitions
Given a session at a state with transitions `needs_full_discovery → event-storming`, `needs_scope_only → scope-boundary`
When the user runs flowr next --session
Then the output shows each trigger name alongside its target state

@id:sf-009
Example: Next shows blocked guarded transitions with condition hints
Given a session at a state with a guarded transition requiring `committed_to_main_locally=verified`
When the user runs flowr next --session without evidence
Then the blocked transition appears with a marker showing the required evidence

@id:sf-010
Example: Next shows passing guarded transitions
Given a session at a state with a guarded transition requiring `committed_to_main_locally=verified`
When the user runs flowr next --session --evidence committed_to_main_locally=verified
Then the transition appears as passing/open

@id:sf-011
Example: Next JSON output uses transitions array with full details
Given a session at a state with transitions
When the user runs flowr next --session --json
Then the output contains a `transitions` array where each entry has `trigger`, `target`, `status`, and `conditions`

Rule: Check Session Shows Conditions
As a flowr user
I want to check transition conditions from session mode
So that I know exactly what evidence to provide

@id:sf-012
Example: Check session with target shows transition conditions
Given a session at a state with a guarded transition
When the user runs flowr check --session <trigger-name>
Then the output shows the conditions required for that transition

Rule: Session-Aware States and Validate
As a flowr user
I want to list states and validate the current flow from session
So that I can inspect my current (sub)flow context without specifying the flow name

@id:sf-013
Example: States command with --session lists current flow's states
Given a session inside architecture-flow
When the user runs flowr states --session
Then the output lists all states in architecture-flow

@id:sf-014
Example: Validate command with --session validates current flow
Given a session inside architecture-flow
When the user runs flowr validate --session
Then the output validates architecture-flow
Loading
Loading