DEV Community

Cover image for Pre-Workflow Conversations: The Controller Pattern in CodeMachine
Hady Walied
Hady Walied

Posted on

Pre-Workflow Conversations: The Controller Pattern in CodeMachine

When building agentic workflows, a common pattern emerges: before the autonomous pipeline runs, someone needs to gather requirements. CodeMachine v0.8.0 introduces the Controller pattern, a first-class way to have a conversation with an AI product owner before your workflow executes.

This walkthrough demonstrates building a spec-driven development workflow where a PO agent gathers requirements, then hands off to specialized agents for analysis, architecture, and implementation.

The Problem

Multi-agent workflows typically start with a prompt and run autonomously. But real development doesn't work that way. Requirements need clarification. Scope needs calibration. Assumptions need validation.

The naive solution: prepend a "requirements gathering" step. But this has issues:

  1. Session management: The PO conversation should persist across interruptions
  2. Handoff clarity: When does conversation end and execution begin?
  3. Return capability: What if you need to talk to the PO mid-workflow?

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    Controller Phase                         │
│              (Interactive PO conversation)                  │
│                                                             │
│   User ←→ spec-po agent (session persisted)                 │
│                                                             │
│   [Enter with empty input] → Confirmation Dialog            │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼ workflow:controller-continue
┌─────────────────────────────────────────────────────────────┐
│                   Execution Phase                           │
│              (Autonomous agent pipeline)                    │
│                                                             │
│   spec-analyst → spec-architect → spec-api-designer → ...   │
│                                                             │
│   [Press C] → Return to Controller Dialog                   │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼ workflow:return-to-controller
┌─────────────────────────────────────────────────────────────┐
│                 Return to Controller                        │
│          (Pause workflow, resume PO session)                │
│                                                             │
│   User ←→ spec-po agent (same session)                      │
│                                                             │
│   [Enter with empty input] → Resume Workflow                │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Defining a Controller

The controller() function declares which agent handles pre-workflow conversation:

export default {
    name: 'Spec-Driven Development',
    controller: controller('spec-po', {}),
    specification: false,

    steps: [
        separator("∴ Discovery Phase ∴"),
        resolveStep('spec-po', {}),
        resolveStep('spec-analyst', {}),

        separator("∴ Design Phase ∴"),
        resolveStep('spec-architect', {}),
        resolveStep('spec-api-designer', {}),

        separator("∴ Implementation Phase ∴"),
        resolveStep('spec-setup', {}),
        resolveStep('spec-impl-orchestrator', {}),

        separator("⟲ Review Loop ⟲"),
        resolveStep('spec-tester', { interactive: false }),
        resolveModule('spec-review', { interactive: false, loopSteps: 2 }),
    ],
};
Enter fullscreen mode Exit fullscreen mode

Key points:

  1. controller('spec-po', {}) - Declares the PO agent for pre-workflow conversation
  2. Same agent in steps - The spec-po agent appears both as controller AND as step 1
  3. Automatic skip - When controller phase runs, step 1 is auto-completed (no redundant execution)

Controller Options

The controller() function accepts options for engine and model overrides:

controller('spec-po', {
    engine: 'claude',           // Override engine
    model: 'claude-4.5-sonnet'      // Override model
})
Enter fullscreen mode Exit fullscreen mode

This is useful when your PO conversation requires different capabilities than the workflow steps.

The Controller Conversation Flow

When you start a workflow with a controller:

┌──────────────────────────────────────────────────────────────┐
│  CodeMachine v0.8.0                                          │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ▸ spec-po (Running)                                         │
│                                                              │
│  What would you like to build today?                         │
│──────────────────────────────────────────────────────────────│
│                                                              │
│  > A todo list app with Next.js and SQLite                   │
│                                                              │
└──────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The conversation continues until you press Enter with an empty input. A confirmation dialog appears:

┌────────────────────────────────────────┐
│   Ready to start the workflow?         │
│                                        │
│   [Start]  [Continue Chat]             │
└────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Selecting "Start" triggers workflow:controller-continue, transitioning to the execution phase.

Session Persistence

The controller session persists to disk:

{
  "controllerConfig": {
    "agentId": "spec-po",
    "sessionId": "ses_44785e25dffeDZqs8kVN7KbfIx",
    "monitoringId": 1
  },
  "autonomousMode": "true"
}
Enter fullscreen mode Exit fullscreen mode

This enables:

  • Resume on crash: If the CLI crashes mid-conversation, the session resumes
  • Return to controller: Mid-workflow return uses the same session
  • Log viewing: Clicking the completed PO step shows the full conversation

Returning to Controller Mid-Workflow

Press C during workflow execution to pause and return to the controller:

┌──────────────────────────────────────────────────────────────┐
│  Workflow Pipeline (8 items)                                 │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ✓ Hady [PO] (completed)                                     │
│  ▸ Moaz [Analyst] (running)         ← Paused                 │
│  ○ Atef [Architect]                                          │
│  ○ Essam [API]                                               │
│                                                              │
├──────────────────────────────────────────────────────────────┤
│  [C] Controller  [Esc] Stop                                  │
└──────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

When C is pressed:

  1. Current step is aborted
  2. Workflow state machine enters PAUSE
  3. Phase switches to "onboarding"
  4. Controller session resumes

After the conversation, pressing Enter resumes the workflow from the paused step.

Complete Workflow Example

Here's the full spec-driven workflow with controller:

export default {
    name: 'Spec-Driven Development',
    controller: controller('spec-po', {}),
    specification: false,

    tracks: {
        question: 'What are we working on?',
        options: {
            new_project: { label: 'New Project' },
            existing_app: { label: 'Existing App' },
            refactor: { label: 'Refactor' },
        },
    },

    conditionGroups: [
        {
            id: 'features',
            question: 'What features does your project have?',
            multiSelect: true,
            conditions: {
                has_ui: { label: 'Has UI' },
                has_auth: { label: 'Has Authentication' },
                has_db: { label: 'Has Database' },
            },
        },
    ],

    steps: [
        separator("∴ Discovery Phase ∴"),
        resolveStep('spec-po', {}),
        resolveStep('spec-analyst', {}),

        separator("∴ Design Phase ∴"),
        resolveStep('spec-architect', {}),
        resolveStep('spec-api-designer', {}),

        separator("∴ Implementation Phase ∴"),
        resolveStep('spec-setup', {}),
        resolveStep('spec-impl-orchestrator', {}),

        separator("⟲ Review Loop ⟲"),
        resolveStep('spec-tester', { interactive: false }),
        resolveModule('spec-review', { interactive: false, loopSteps: 2 }),
    ],

    subAgentIds: [
        'spec-dev-data',
        'spec-dev-api',
        'spec-dev-ui',
        'spec-dev-tests',
    ],
};
Enter fullscreen mode Exit fullscreen mode

Key Benefits

Requirement Solution
Requirements clarification Controller conversation before execution
Session persistence SessionID stored in template.json
Clear handoff Confirmation dialog before transition
Mid-workflow return C key pauses and resumes controller session
Log viewing MonitoringID registered for completed step
Same agent, no duplication Controller step auto-completed after phase

Conclusion

The Controller pattern addresses a fundamental gap in agentic workflows: the need for human-AI conversation before autonomous execution. By treating this as a first-class primitive, with session persistence, clear handoff UX, and mid-workflow return capability, CodeMachine enables workflows that match how real development actually works.


CodeMachine on Github: moazbuilds/codemachinecli

Workflow Examples: github.com/hadywalied/codemachine_example_workflows

Documentation: codemachine.co

Top comments (0)