Skip to content

Latest commit

 

History

History
569 lines (418 loc) · 19 KB

File metadata and controls

569 lines (418 loc) · 19 KB

Quality Gates

Purpose: Control transitions between phases with configurable approval requirements.


Automation-First Philosophy

Orc defaults to fully automated gates - the system runs without human intervention by default. Human gates are opt-in for workflows that require oversight.

Quality assurance is handled by backpressure (tests, lint, build), not LLM-based evaluation. This provides deterministic, repeatable quality checks.


Gate Types

Type Description Use Case
auto Proceed immediately if criteria met Default for all phases
human Requires manual approval Critical decisions
skip No gate, always continues Fast iteration
ai AI agent evaluates gate Automated review/validation

Gate Modes

Mode Behavior Use Case
gate Synchronous, blocks phase progression until resolved Default - validation before proceeding
reaction Asynchronous, fire-and-forget Notifications, logging, non-blocking checks

Location: internal/workflow/types.go:40-46


Gate Actions

Actions define what happens on gate approval or rejection:

Action Behavior
continue Continue to next phase
retry Retry from specified phase (see retry_from)
fail Fail the task
skip_phase Skip the next phase
run_script Execute a script (see script)

Location: internal/workflow/types.go:48-57

Action Dispatch (Executor)

Actions are resolved via resolveApprovedAction()/resolveRejectedAction() in internal/executor/gate_actions.go and dispatched in the main phase loop (internal/executor/workflow_executor.go).

On Rejection:

Configured Action Executor Behavior
fail Task fails immediately
retry Retries from retry_from phase; falls back to fail if max retries exceeded or no retry_from
skip_phase / continue Proceeds to next phase (rejection ignored)
run_script Script already executed during gate evaluation; applies fail as secondary action
(empty) Legacy behavior: review phase → fail, other phases → continue

On Approval:

Configured Action Executor Behavior
continue / (empty) Default — proceed to next phase
skip_phase Marks the NEXT phase as SKIPPED and advances past it
run_script Script already executed during gate evaluation; secondary is continue

Script execution: Scripts run during evaluatePhaseGate() via runGateScript() (workflow_gates.go:145). Script output can override the gate decision (flip approved↔rejected). Infrastructure errors (missing script, timeout) log warnings and continue.

RetryFrom precedence: GateOutputConfig.RetryFrom > PhaseTemplate.RetryFromPhase > config retry map. See resolveRetryFrom() at gate_actions.go:44.


Gate Input/Output Configuration

GateInputConfig

Controls what context flows into the gate evaluator:

Field Type Purpose
include_phase_output []string Phase IDs whose output to include
include_task bool Include task details in context
extra_vars []string Additional variable names to pass

GateOutputConfig

Controls what happens with gate evaluation results:

Field Type Purpose
variable_name string Store result in workflow variable
on_approved GateAction Action when gate approves
on_rejected GateAction Action when gate rejects
retry_from string Phase to retry from (when action=retry)
script string Script path (when action=run_script)

Location: internal/workflow/types.go:69-83, DB layer: internal/db/gate_config.go


Trigger System

Triggers are agent-backed hooks that fire at specific points in the task lifecycle. They use the same gate infrastructure (modes, input/output config) as phase gates.

Implementation: internal/trigger/ package. See internal/trigger/CLAUDE.md for package details.

Trigger Modes

Mode Behavior Error Handling
gate Synchronous, blocks progression if rejected Returns GateRejectionError
reaction Async goroutine, fire-and-forget Errors logged, never blocks

Default mode is gate when unspecified.

TriggerRunner (Shared Component)

Single TriggerRunner used by executor, CLI, and API for consistent trigger evaluation.

Call Site File Event
Executor (before phase) executor/workflow_triggers.go:19 Before each phase
Executor (completion) executor/workflow_triggers.go:95 on_task_completed
Executor (failure) executor/workflow_triggers.go:76 on_task_failed
CLI task creation cli/cmd_trigger.go on_task_created
CLI initiative plan cli/cmd_initiative_plan.go on_initiative_planned
API task creation api/task_server.go on_task_created

API CRUD: api/workflow_server_lifecycle_trigger.go — Add/Update/Remove lifecycle triggers on workflows. See docs/API_REFERENCE.md → Lifecycle Triggers.


Before-Phase Triggers

Triggers that run before a phase starts, enabling pre-validation or preparation:

workflow_phases:
  - phase_template_id: implement
    before_triggers:
      - agent_id: "dependency-check"
        mode: gate               # blocks if agent rejects
        input_config:
          include_task: true
        output_config:
          on_rejected: fail
          variable_name: "DEP_CHECK_RESULT"  # output flows to variable
Field Type Purpose
agent_id string Agent to execute
input_config GateInputConfig Context for the agent
output_config GateOutputConfig Result handling + variable capture
mode GateMode gate (blocking) or reaction (async)

Error resilience (SC-1): Infrastructure errors (agent crash, parse failure) log a warning and continue the phase. Only explicit gate rejections block.

Location: internal/workflow/types.go:85-91, evaluation: internal/trigger/runner.go:128


Workflow Lifecycle Triggers

React to task/initiative lifecycle events at the workflow level:

workflows:
  triggers:
    - event: on_task_completed
      agent_id: "notify-slack"
      mode: reaction
      enabled: true
    - event: on_task_failed
      agent_id: "failure-analyzer"
      mode: gate
      input_config:
        include_task: true
Event Fires When Gate Rejection Effect
on_task_created Task is created (CLI or API) Task status set to BLOCKED
on_task_completed Task completes all phases Task status set to BLOCKED instead of COMPLETED
on_task_failed Task fails during execution Logged as warning
on_initiative_planned Initiative planning creates tasks Error returned to caller

Built-in trigger: The dependency-validator agent runs on on_initiative_planned in implement-medium and implement-large workflows. It detects missing blocked_by dependencies between initiative tasks by analyzing descriptions/specs for code-level ordering requirements. See templates/agents/dependency-validator.md.

Location: internal/workflow/types.go:59-101, evaluation: internal/trigger/runner.go:55, initiative trigger: internal/trigger/runner.go:207


Automation Profiles

Profile Default Gate Description
auto All auto Default - Full automation, no human approval
fast All auto + no pre-merge Maximum speed, no retry on failure
safe Auto + human merge Balanced - Automatic until final merge
strict Human on spec/merge Full oversight for critical phases
# Run with profile
orc run TASK-001 --profile auto    # (default)
orc run TASK-001 --profile safe    # human on merge
orc run TASK-001 --profile strict  # human on spec/merge

Default Gates by Weight (auto profile)

Phase Trivial Small Medium Large Greenfield
research - - - auto auto
spec - - - auto auto
implement auto auto auto auto auto
test auto auto auto auto auto
validate - - - auto auto

Gate Configuration

# orc.yaml - default automation-first configuration
gates:
  default_type: auto              # Default gate type for all phases
  auto_approve_on_success: true   # Auto-approve when phase succeeds
  retry_on_failure: true          # Enable cross-phase retry
  max_retries: 3                  # Max retry attempts per phase

  # Override specific phases
  phase_overrides:
    merge: human                  # Human approval before merge

  # Override by weight
  weight_overrides:
    greenfield:
      spec: human                 # Human review for greenfield specs

# Cross-phase retry configuration
retry:
  enabled: true
  max_retries: 3
  retry_map:
    test: implement              # Test failures retry from implement
    validate: implement          # Validation failures retry from implement

Extended Gate Configuration

Phase templates and workflow phases support additional gate fields:

# Phase template with AI gate
phase_templates:
  - id: security-review
    gate_type: ai
    gate_agent_id: "security-reviewer"
    gate_mode: gate
    gate_input_config:
      include_phase_output: ["implement"]
      include_task: true
    gate_output_config:
      variable_name: "SECURITY_RESULT"
      on_approved: continue
      on_rejected: retry
      retry_from: implement

Gate Output Variable Pipeline

Gate evaluations can produce structured output data that flows into the workflow variable system for use in subsequent phases.

Data Flow

GateAgentResponse.data → Decision.OutputData + Decision.OutputVar
  → GateEvaluationResult.OutputData/OutputVar
    → applyGateOutputToVars()
        ├── vars[OutputVar] = JSON(OutputData)      # Immediate use
        └── rctx.PhaseOutputVars[OutputVar] = JSON  # Survives ResolveAll()
      → {{VARIABLE_NAME}} in subsequent phase prompts (including retry)

Configuration

gate_output_config:
  variable_name: "SECURITY_RESULT"   # Variable name for downstream access
  on_approved: continue
  on_rejected: retry
  retry_from: implement

Behavior

Scenario Output Stored? Details
Gate approved with output data Yes applyGateOutputToVars() stores JSON to vars[variable_name] + rctx.PhaseOutputVars
Gate rejected with output data Yes Output stored even on rejection (available to retry phase)
Retry triggers ResolveAll() Yes Variables restored from rctx.PhaseOutputVars by addBuiltinVariables()
No variable_name configured No Silently skipped
No output data from gate No Silently skipped
JSON serialization error No Logged warning, execution continues

Retry Survival Guarantee

Gate output variables survive the retry flow. When a gate rejects and triggers retry from an earlier phase, ResolveAll() creates a fresh vars map. Gate output variables are restored from rctx.PhaseOutputVars (populated by applyGateOutputToVars()), making them available in the retried phase's prompt.

Why this matters: Without persistence to rctx.PhaseOutputVars, gate output would be lost during retry. Templates like {{GATE_REVIEW}} would render empty, losing the AI reviewer's feedback that should guide the fix.

Implementation: executor/workflow_gates.go:235 (output storage), variable/resolver.go:403 (restoration via addBuiltinVariables())


Cross-Phase Retry

When a gate rejects or a phase fails, orc can automatically retry from an earlier phase:

implement → test (FAIL) → implement (retry #1) → test → validate

The retry phase receives structured retry variables ({{RETRY_ATTEMPT}}, {{RETRY_FROM_PHASE}}, {{RETRY_REASON}}) in its prompt, enabling the agent to fix the root cause rather than just re-running blindly.


Auto Gate Criteria

Auto gates check deterministic criteria against phase output:

Criterion Description
has_output Phase produced non-empty output
no_errors Output doesn't contain "error"
has_completion_marker JSON response has {"status": "complete"}
Custom string Check if string appears in output
# Plan YAML - auto gate with criteria
phases:
  - id: implement
    gate:
      type: auto
      criteria:
        - has_output
        - has_completion_marker

Human Gate Workflow

Human gates work differently depending on whether the task is running interactively (CLI) or headlessly (API/WebSocket).

Interactive Mode (CLI)

When running via orc run in a terminal:

[GATE] Human approval required for merge

Task: TASK-001 - Add user authentication
Phase: merge
Files changed: 8
Tests: 24 passing

Approve? [y/n/q(questions)]: _

The CLI blocks and waits for input. Enter y to approve, n to reject with reason, or q to ask clarifying questions.

Headless Mode (API/WebSocket)

When running via the API (e.g., from the web UI), gates don't block:

  1. Task hits human gate - Gate evaluator detects headless mode
  2. Task blocked - Status changes to blocked
  3. Event emitted - decision_required WebSocket event broadcast
  4. User notified - Web UI shows approval prompt
  5. User decides - Frontend calls POST /api/decisions/:id
  6. Decision recorded - State and database updated
  7. Status updated - Task becomes planned (approved) or failed (rejected)
  8. Resume required - User must explicitly resume the task
┌─────────────┐    ┌──────────────┐    ┌─────────────────────┐
│  Executor   │───▶│   Gate       │───▶│ PendingDecisionStore│
│ (phase run) │    │ (human gate) │    │ (in-memory map)     │
└─────────────┘    └──────────────┘    └─────────────────────┘
                          │                      │
                          ▼                      ▼
                   ┌──────────────┐    ┌─────────────────────┐
                   │  Publisher   │    │  POST /decisions/:id│
                   │ (emit event) │    │  (resolve decision) │
                   └──────────────┘    └─────────────────────┘
                          │                      │
                          ▼                      ▼
                   ┌───────────────────────────────────────────┐
                   │         WebSocket Subscribers             │
                   │  (receive decision_required/resolved)     │
                   └───────────────────────────────────────────┘

Note: Pending decisions are stored in-memory. Server restart clears them; tasks remain blocked and can be resolved via orc approve CLI.

Notification Channels

  1. Terminal (if interactive):

    [GATE] Human approval required for merge
    
    Task: TASK-001 - Add user authentication
    Phase: merge
    Files changed: 8
    Tests: 24 passing
    
    orc approve TASK-001    # Approve
    orc reject TASK-001     # Reject with reason
    orc diff TASK-001       # View changes
    
  2. WebSocket Event (if headless):

    {
      "type": "event",
      "event_type": "decision_required",
      "data": {
        "decision_id": "gate_TASK-001_merge",
        "task_id": "TASK-001",
        "task_title": "Add user authentication",
        "phase": "merge",
        "gate_type": "human",
        "question": "Please verify the following criteria:",
        "context": "Code review passes\nTests pass",
        "requested_at": "2026-01-10T10:30:00Z"
      }
    }
  3. Desktop Notification (if configured)

  4. Webhook (Slack, email, etc.)

Approval Commands

# Approve current gate
orc approve TASK-001

# Approve with comment
orc approve TASK-001 --comment "LGTM"

# Reject with reason (required)
orc reject TASK-001 --reason "Missing error handling"

# View what's pending
orc status --waiting

Gate Inspection Commands

# List gate config for all workflow phases (table or JSON)
orc gates list
orc gates list --json

# Show detailed config for a specific phase
orc gates show <phase>

# View gate decision history for a task
orc show TASK-001 --gates

# Skip all gates during execution (dev/testing)
orc run TASK-001 --skip-gates

Implementation: internal/cli/cmd_gates.go (list/show), internal/cli/cmd_show.go (--gates flag), internal/cli/cmd_run.go (--skip-gates flag)


Gate Audit Trail

# Database: states table
gates:
  - phase: spec
    type: auto
    decision: approved
    timestamp: 2026-01-10T10:45:00Z

  - phase: merge
    type: human
    decision: approved
    timestamp: 2026-01-10T15:45:00Z
    approver: randy
    comment: "Tested locally, looks good"

Quality Checks (Phase-Level)

Quality is validated through phase-level quality checks - deterministic checks that run after a phase claims completion.

Configuration

Quality checks are defined per phase template in the database:

[
  {"type": "code", "name": "tests", "enabled": true, "on_failure": "block"},
  {"type": "code", "name": "lint", "enabled": true, "on_failure": "block"},
  {"type": "code", "name": "build", "enabled": true, "on_failure": "block"},
  {"type": "code", "name": "typecheck", "enabled": true, "on_failure": "block"}
]

Check Types

Type Behavior
code Looks up command from project_commands table by name
custom Uses the command field directly

On-Failure Modes

Mode Behavior
block Phase fails, context injected for retry
warn Warning logged, completion accepted
skip Check disabled

Project Commands

Commands are seeded during orc init based on project detection and stored in the project_commands database table. Manage with orc config commands.

Quality checks provide objective, repeatable quality validation without LLM judgment calls.

See internal/executor/quality_checks.go for implementation.


Emergency Override

# Force approval (logged with reason)
orc approve TASK-001 --force --reason "P0 hotfix"

Creates audit entry:

- phase: merge
  type: human
  decision: override
  approver: randy
  override_reason: "P0 hotfix"
  timestamp: 2026-01-10T03:00:00Z