Skip to content

v0.2.0: Redesign scheduler as Finite APE Machine#146

Merged
ccisnedev merged 26 commits intomainfrom
release/0.2.0
Apr 27, 2026
Merged

v0.2.0: Redesign scheduler as Finite APE Machine#146
ccisnedev merged 26 commits intomainfrom
release/0.2.0

Conversation

@ccisnedev
Copy link
Copy Markdown
Owner

Closes #145

Summary

Redesign the Inquiry CLI scheduler from a 554-line monolith agent to a modular Finite APE Machine with dual FSM architecture (main + per-APE).

Architecture: CLI = Kernel, Agent = Firmware

  • CLI (kernel): owns all state, validates all transitions, executes all effects
  • Agent (firmware): ~35 lines, reads state via \iq fsm state --json, dispatches sub-agents via \iq ape prompt\
  • Sub-agents (YAMLs): socrates, descartes, basho, darwin — each with internal FSM (transitions, _DONE\ sentinel)

New Commands

Command Purpose
\iq fsm state [--json]\ Current FSM state + transitions + active APE info
\iq fsm transition --event \ Validate + execute main FSM transition with effects
\iq ape state [--json]\ Active APE sub-state + internal transitions
\iq ape transition --event \ Validate + execute APE internal transition
\iq ape prompt --name \ Assemble sub-agent prompt (auto-reads sub-state)

Key Features

  • Auto-activation: transitioning to ANALYZE auto-activates socrates:clarification
  • Backward-compatible state.yaml: old format (no \�pe:\ field) still works
  • Semantic exit codes: notFound=4, conflict=6, validationFailed=7
  • Effect executor: update_state, reset_mutations, snapshot_metrics, collect_metrics, close_cycle

QA

  • 290 unit tests, \dart analyze\ clean
  • Full e2e cycle tested on Linux (devcontainer) and Windows: 14/14 sections PASS
  • 3 bugs found during QA and fixed (BUG-1: error handling, BUG-2: APE state preservation, BUG-3: missing flag validation)

Checklist

  • All tests pass (290)
  • CHANGELOG updated
  • Version bumped (0.1.3 → 0.2.0)
  • Site badge updated
  • Linux e2e QA: 14/14 PASS
  • Windows parity verified

Phase 0 bug fixes for #145:

Bug 0.1 — doctor reports '0 skills deployed':
  Root cause: global_builder.dart created DoctorCommand without passing
  Assets, so _assets was null and _getExpectedSkills() returned [].
  Fix: inject Assets through buildGlobalModule into DoctorCommand.
  Also extract assetsRoot in inquiry_cli.dart to eliminate duplication.

Bug 0.2 — init creates docs\cleanrooms:
  Code already correct (uses root/cleanrooms). Not reproducible.
  Added regression test: assert docs/cleanrooms does NOT exist.
Phase 1 for #145:
- Rename lib/modules/state/ → lib/modules/fsm/
- Rename state_builder.dart → fsm_builder.dart, buildStateModule → buildFsmModule
- CLI route: iq state transition → iq fsm transition
- Rename test files: state_transition_*.dart → fsm_transition_*.dart
- Update all imports
- 161 tests pass, 0 analysis issues
Phase 2 for #145:
- New FsmStateCommand reads .inquiry/state.yaml
- Output: {state, task, transitions[], apes[], instructions}
- Transitions filtered from contract (allowed only)
- APE mapping: ANALYZE→socrates, PLAN→descartes, EXECUTE→basho,
  END→basho, EVOLUTION→darwin, IDLE→[]
- Registered as 'iq fsm state' in fsm_builder
- 14 new tests, 175 total, 0 issues
Unify .inquiry/state.yaml format:
- phase: → state:  (consistent with iq fsm state output)
- task: → issue:   (value is always a GitHub issue number)
- Migrate transition command from .ape/state.yaml to .inquiry/state.yaml
- Migrate _isIssueSelected from .ape/context.yaml to .inquiry/state.yaml
- Remove legacy cycle: wrapper from init output
- Update all tests (175 pass, 0 issues)
Both FsmStateCommand and StateTransitionCommand were resolving
transition_contract.yaml relative to workingDirectory instead of
the binary's assets directory. In production the contract lives
at <install>/assets/fsm/, not at <project>/assets/fsm/.

Fix: inject Assets through buildFsmModule, fall back to workingDirectory
for tests that set up their own assets directory.
Phase 3 for #145:
- New EffectExecutor handles CLI-side effects:
  update_state, reset_mutations, snapshot_metrics, close_cycle, collect_metrics
- Skill-side effects (push_branch, create_pull_request, etc.) reported but not executed
- Integrated into StateTransitionCommand: effects execute after validation
- operationsExecuted now reflects actually executed effects
- 12 new tests (187 total), all manual tests pass
Phase 4: Extract sub-agent prompts from monolithic inquiry.agent.md
into individual YAML definitions.

- assets/apes/socrates.yaml (6 states: clarification → meta_reflection)
- assets/apes/descartes.yaml (4 states: decomposition → enumeration)
- assets/apes/basho.yaml (3 states: implement → commit)
- assets/apes/darwin.yaml (4 states: observe → report)
- lib/modules/ape/ape_definition.dart: parser with assemblePrompt()
- 15 tests: schema validation, state counts, prompt fidelity, assemblePrompt

202 tests passing, 0 issues.
Phase 5: Assemble sub-agent prompts from YAML + FSM state.

- lib/modules/ape/commands/prompt.dart: ApePromptCommand
  - --name <ape>: required, selects sub-agent YAML
  - --state <sub_state>: optional, appends state-specific prompt
  - APE_NOT_FOUND / APE_NOT_ACTIVE validation
- lib/modules/ape/ape_builder.dart: registers ape module
- inquiry_cli.dart: cli.module('ape', ...)
- 20 tests: assembly, errors, fidelity, output format
- Manual: iq ape prompt --name basho ✓, --state implement ✓

222 tests passing, 0 issues.
Decision: Formal FSM per-APE following RTOS model.
- Each APE gets internal transitions in YAML
- state.yaml extended with ape: {name, state}
- New commands: iq ape state, iq ape transition
- Auto-activation on main FSM transition
- iq ape prompt reads sub-state automatically
- Firmware loop becomes dual (outer FSM + inner APE FSM)
- Add transitions + initial_state to all APE YAMLs (socrates, descartes, basho, darwin)
- Extend ApeDefinition parser: ApeTransition, initialState, findState(), chain walking
- Add InquiryState persistence helper (read/write state.yaml with ape: field)
- Add iq ape state command (reports sub-state + valid transitions)
- Add iq ape transition --event command (validates + executes APE internal transitions)
- Auto-activation: EffectExecutor writes ape:{name,state} on main FSM transitions
- iq ape prompt auto-reads sub-state from state.yaml when --state omitted
- iq fsm state --json includes ape:{name,state,transitions[]} field
- Migrate EffectExecutor, FsmStateCommand, ApePromptCommand to use InquiryState
- 275 tests (45 new), dart analyze clean
…Phase 6)

- New inquiry.agent.md: ~35 lines, dual FSM loop (outer + inner)
- Firmware reads state via iq fsm state, dispatches sub-agents via iq ape prompt
- Never writes .inquiry/ directly, all mutations through iq commands
- Archive monolith as assets/archive/inquiry.agent.md.legacy
- 10 firmware content tests (thin, no leakage, all commands referenced)
- 285 tests total, dart analyze clean
- Update _ensureStateYaml to write new format with ape: null
- Skills already shell-agnostic (7.2 no-op)
- Agent template already deployed by TargetDeployer (7.3 no-op)
- Shell detection deferred (skills use iq/git/gh, all cross-platform)
- Updated init test to verify ape: null in generated state.yaml
- 285 tests, dart analyze clean
…eserve APE sub-state on same-APE FSM transitions

Phase 8b — 3 bugs found during e2e QA on Linux:

BUG-1 (MEDIUM): ape transition/prompt domain errors threw StateError
  → unhandled stacktrace + exit 255. Now throws CommandException with
  semantic exit codes (conflict=6, notFound=4, validationFailed=7).

BUG-2 (LOW): EXECUTE→END re-initialized basho to 'implement' instead
  of preserving '_DONE'. updateState() now checks if same APE continues
  and preserves its sub-state.

BUG-3 (MEDIUM): Missing --event/--name flags threw ArgumentError from
  factory constructors. Input fields now nullable, validation moved to
  execute() with CommandException(MISSING_EVENT/MISSING_NAME).

Tests: 290 pass (285 + 5 new), dart analyze clean.
Plan: Phase 8b integrated into plan.md with TDD checklist.
- FSM module (iq fsm state/transition) with effect execution
- RTOS dual-FSM: main FSM + per-APE internal FSM with auto-activation
- Sub-agent YAMLs (socrates, descartes, basho, darwin) with transitions
- APE commands: iq ape prompt, iq ape state, iq ape transition
- Firmware thin agent (~35 lines replaces 554-line monolith)
- InquiryState persistence with ape: field (backward-compatible)
- Devcontainer for Linux e2e testing
- 290 tests, QA 14/14 PASS on Linux + Windows
- dev-install.sh/ps1: build from source and install to standard layout
- .gitattributes: normalize line endings (LF in repo, native in working tree)
Bug: 'iq fsm transition --event start_analyze --issue 31' silently
ignored the --issue flag. The issue number was never written to
state.yaml because StateTransitionInput did not parse it.

Fix:
- Add 'issue' field to StateTransitionInput + parse --issue/-i flag
- Pass issue to EffectExecutor.executeAll()
- Two new tests: persists issue on transition, preserves existing issue

Found during smoke test of v0.2.0 firmware in tareas devcontainer.
BUG-A: Inner Loop paso 2 usaba 'Become that sub-agent', haciendo que el
scheduler ejecutara el trabajo del sub-agente en su propio contexto.
Resultado: contenido del plan renderizado en chat (F2) y scheduler
escribiendo plan.md en lugar del sub-agente (F4).

Fix: reemplazar 'Become' por 'Dispatch' — el scheduler invoca @<ape>
via agent tool y espera señal de completion. No ejecuta, no renderiza.
BUG-B: 'ask user to approve' sin restriccion de formato generaba
preguntas abiertas (F3) y preguntas compuestas (F8).

Fix:
- Inner Loop paso 4: exactamente UNA pregunta binaria yes/no, sin
  alternativas ni preguntas compuestas.
- Nueva seccion END Checkpoint: una pregunta binaria antes del PR,
  sin mencionar EVOLUTION, transicion automatica por config.yaml.
BUG-D: 'NEVER change state without explicit user authorization' usaba
'state' en sentido amplio. El LLM lo aplicaba tambien a commits (F6).

Fix: delimitar la regla a 'iq fsm transition' y 'iq ape transition'.
Commits, pushes y operaciones de archivos dentro de una sub-fase son
autonomos. BUG-C: la regla NEVER-write se deja intacta (la edicion
directa de state.yaml fue consecuencia del bug F1, no de la regla).
… LLM

BUG-D/BUG-F: The firmware relied on LLM interpretation for knowing
when to ask the user vs transition automatically. This led to F6
(authorization asked for commits), F8/F9 (EVOLUTION offered as choice).

Design: Each FSM state has a completion_authority (user | automatic).
- user: the state produces subjective work; user decides when complete.
- automatic: deterministic completion; transition fires immediately.

Implementation:
- transition_contract.yaml: new completion_authority map per state
- fsm_contract.dart: parse new field into FsmContract
- state.dart: include completion_authority in JSON output + filter
  END transitions by evolution.enabled from config.yaml (scheduler
  only sees ONE valid transition from END, no choice needed)
- firmware: rule based on completion_authority field, no exceptions
- issue-end skill: removed all evolution.enabled references
- 8 new tests, 299 total all passing
…nces

BUG-E: issue-end/SKILL.md contained hard-coded paths (lib/src/version.dart),
variable names (inquiryVersion), and commands (dart analyze, dart test)
specific to the Inquiry project. When deployed to other repos, the LLM
detected the mismatch and named the meta-project explicitly (F7).

Fix: replace all stack-specific references with generic patterns:
- 'grep inquiryVersion lib/src/version.dart' -> locate project version file
- 'pubspec.yaml + lib/src/version.dart' -> project-specific version files
- 'dart test / dart analyze' -> all tests / static analysis
- Step 4 now lists common manifest patterns across ecosystems
@ccisnedev ccisnedev merged commit 48b6c1b into main Apr 27, 2026
2 checks passed
@ccisnedev ccisnedev deleted the release/0.2.0 branch April 27, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

release: v0.2.0 — extract inquiry_core package, stabilize FSM contract

1 participant