Skip to content

Pipeline Plan 414

ezigus edited this page Apr 22, 2026 · 2 revisions

Implementation Plan: Wire RUFLO_COST_BUDGET_MULTIPLIER into Hive Agent Counts

Issue: #414
Feature: feat(ruflo): [01.1] wire RUFLO_COST_BUDGET_MULTIPLIER into hive agent counts
Complexity: Low (15 lines, 3 functions, Phase 01 infrastructure wiring)
Risk: Minimal (backward-compatible, fail-open design)


1. Task Decomposition (with Dependencies)

Task 1: Apply multiplier to ruflo_execute_review()
  ├─ Depends on: (none)
  └─ Blocks: Task 4

Task 2: Apply multiplier to ruflo_execute_compound_quality()
  ├─ Depends on: (none)
  └─ Blocks: Task 4

Task 3: Apply multiplier to ruflo_execute_audit()
  ├─ Depends on: (none)
  └─ Blocks: Task 4

Task 4: Add unit tests for multiplier behavior
  ├─ Depends on: Tasks 1, 2, 3
  ├─ Tests: unset, 2.0 scale-up, 0.5 scale-down, boundary conditions
  └─ Blocks: Task 5

Task 5: Run full test suite (npm test + scripts/sw-ruflo-adapter-test.sh)
  ├─ Depends on: Tasks 1, 2, 3, 4
  └─ Blocks: (PR submission)

2. Risk Analysis

Risk Impact Likelihood Mitigation
Variable scope collision Reassigning max_agents shadows outer scope Low Inspect variable usage post-assignment; max_agents is function-local only
Multiplier < 1 allowing 0 agents Pipeline spawns no agents, stages hang Low awk formula enforces v<1?1 (min 1)
Multiplier > 1 exceeding hard cap Spawn count violates resource constraints Low awk formula enforces v>d?d:v (cap at original)
awk calculation failure Fallback needed if awk errors Medium Fallback: || echo "$max_agents" preserves original
Unset multiplier breaking backward compat Existing unset behavior changes Low Guard: if [[ -n "${RUFLO_COST_BUDGET_MULTIPLIER:-}" ]] ensures no-op when unset
Test coverage gaps Edge cases uncovered Low Add parametrized tests for 5+ scenarios (0, 0.5, 1.0, 2.0, 3.0)
Breaking existing tests Regression in other adapters Very Low Three functions are isolated; no shared state modified

Mitigation Summary:

  • All multiplier application is guarded by if [[ -n "${RUFLO_COST_BUDGET_MULTIPLIER:-}" ]] → unset = no-op
  • awk formula with bounds checks (v<1?1:(v>d?d:v)) → min 1, max hard_cap
  • Fallback to original max_agents if awk fails
  • Comprehensive unit tests covering 5+ scenarios
  • Run full test suite before PR submission

3. Definition of Done

  • Code Changes: Multiplier applied to all 3 functions (ruflo_execute_review, ruflo_execute_compound_quality, ruflo_execute_audit)
  • Backward Compatibility: Unset RUFLO_COST_BUDGET_MULTIPLIER behaves identically to pre-implementation (no multiplier applied)
  • Scale-Up Scenario: RUFLO_COST_BUDGET_MULTIPLIER=2.0 → agent count increases up to hard cap, never exceeds
  • Scale-Down Scenario: RUFLO_COST_BUDGET_MULTIPLIER=0.5 → agent count reduces proportionally, minimum 1 agent
  • Boundary Handling: Multiplier < 1 enforces min 1 agent; multiplier > 1 respects hard cap
  • Unit Tests Pass: All new tests in sw-ruflo-adapter-test.sh pass (unset, 2.0, 0.5, 0, 3.0)
  • Full Test Suite Passes: npm test and scripts/sw-ruflo-adapter-test.sh pass without regressions
  • No Breaking Changes: Existing pipeline behavior unchanged when multiplier is unset
  • Error Handling: awk failures gracefully fallback to original max_agents
  • Code Quality: Changes follow existing code style (local scoping, spacing, comments)

4. Alternatives Considered

Option 1: Inline Multiplier Application ✅ CHOSEN

local max_agents="${RUFLO_REVIEW_MAX_AGENTS:-${RUFLO_MAX_AGENTS:-4}}"
if [[ -n "${RUFLO_COST_BUDGET_MULTIPLIER:-}" ]]; then
    local _default_max="$max_agents"
    max_agents=$(awk -v d="$_default_max" -v m="${RUFLO_COST_BUDGET_MULTIPLIER}" \
        'BEGIN{v=int(d*m); print (v<1?1:(v>d?d:v))}' 2>/dev/null || echo "$max_agents")
fi

Pros:

  • Explicit, self-documenting behavior
  • Local variable reassignment is clear
  • Reuses provided implementation from issue
  • Minimal additional complexity
  • Easy to reason about within each function

Cons:

  • Code duplication across 3 functions
  • If formula needs change, 3 places to update

Blast Radius: Low — only modifies local max_agents variable; no global state changes


Option 2: Extract Helper Function _apply_multiplier()

_apply_multiplier() {
    local default_max="$1"
    if [[ -n "${RUFLO_COST_BUDGET_MULTIPLIER:-}" ]]; then
        awk -v d="$default_max" -v m="${RUFLO_COST_BUDGET_MULTIPLIER}" \
            'BEGIN{v=int(d*m); print (v<1?1:(v>d?d:v))}' 2>/dev/null || echo "$default_max"
    else
        echo "$default_max"
    fi
}

# Usage in each function:
max_agents=$(_apply_multiplier "$max_agents")

Pros:

  • DRY principle — single source of truth
  • Easier future maintenance (change once, affects all 3)
  • Cleaner function bodies

Cons:

  • Adds abstraction layer (extra function call overhead)
  • Function definition adds ~10 lines
  • Less explicit — requires reading helper to understand behavior
  • Subshell call for each agent spawn (small perf cost)

Blast Radius: Low-Medium — helper function is global but fail-open


Option 3: Use Shell Arithmetic + Bash Math

local _new_max=$(( (max_agents * ${RUFLO_COST_BUDGET_MULTIPLIER:-1} + 0.5) ))  # Rounding
[[ $_new_max -lt 1 ]] && _new_max=1
[[ $_new_max -gt $max_agents ]] && _new_max=$max_agents
max_agents=$_new_max

Pros:

  • No awk dependency
  • Inline arithmetic

Cons:

  • Bash arithmetic truncates (loses fractional part)
  • Verbose compared to awk
  • Repeated bounds checking logic
  • Less precise rounding behavior

Blast Radius: Low — but reduces code quality


Decision Rationale: Chose Option 1 (Inline) because:

  • The issue explicitly provides the implementation pattern
  • Phase 01 is "Wire Missing Connections" — low-risk, high-clarity changes
  • Duplication of 3×5 lines is acceptable (15 lines total) vs. helper overhead
  • Each function is independent; changes are localized
  • DRY can be refactored in Phase 02 if needed
  • Explicit inline code is easier to audit for correctness

5. Files to Modify

File Lines Change Type Scope
scripts/lib/ruflo-adapter.sh 816, 961, 1069 Add 6 lines after each max_agents= Apply multiplier in each function
scripts/sw-ruflo-adapter-test.sh ~250-300 (end of file) Add ~80-100 lines Unit tests for multiplier behavior

No new files to create — all changes are in existing modules.


6. Implementation Steps

Step 1: Apply Multiplier to ruflo_execute_review()

Location: scripts/lib/ruflo-adapter.sh:816

Before:

local max_agents="${RUFLO_REVIEW_MAX_AGENTS:-${RUFLO_MAX_AGENTS:-4}}"
# Use pipeline_id when available...

After:

local max_agents="${RUFLO_REVIEW_MAX_AGENTS:-${RUFLO_MAX_AGENTS:-4}}"
# Apply cost budget multiplier if set
if [[ -n "${RUFLO_COST_BUDGET_MULTIPLIER:-}" ]]; then
    local _default_max="$max_agents"
    max_agents=$(awk -v d="$_default_max" -v m="${RUFLO_COST_BUDGET_MULTIPLIER}" \
        'BEGIN{v=int(d*m); print (v<1?1:(v>d?d:v))}' 2>/dev/null || echo "$max_agents")
fi
# Use pipeline_id when available...

Semantic: Multiplier 2.0 scales 4→8 (capped to hard limit); 0.5 scales 4→2


Step 2: Apply Multiplier to ruflo_execute_compound_quality()

Location: scripts/lib/ruflo-adapter.sh:961

Before:

local cq_agents="${RUFLO_CQ_MAX_AGENTS:-${RUFLO_MAX_AGENTS:-3}}"

emit_event "ruflo.cq_start"

After:

local cq_agents="${RUFLO_CQ_MAX_AGENTS:-${RUFLO_MAX_AGENTS:-3}}"
# Apply cost budget multiplier if set
if [[ -n "${RUFLO_COST_BUDGET_MULTIPLIER:-}" ]]; then
    local _default_max="$cq_agents"
    cq_agents=$(awk -v d="$_default_max" -v m="${RUFLO_COST_BUDGET_MULTIPLIER}" \
        'BEGIN{v=int(d*m); print (v<1?1:(v>d?d:v))}' 2>/dev/null || echo "$cq_agents")
fi

emit_event "ruflo.cq_start"

Semantic: Multiplier applied to adversarial quality agent pool (default 3)


Step 3: Apply Multiplier to ruflo_execute_audit()

Location: scripts/lib/ruflo-adapter.sh:1069

Before:

local max_agents="${RUFLO_AUDIT_MAX_AGENTS:-${RUFLO_MAX_AGENTS:-4}}"

emit_event "ruflo.audit_start" "max_agents=$max_agents"

After:

local max_agents="${RUFLO_AUDIT_MAX_AGENTS:-${RUFLO_MAX_AGENTS:-4}}"
# Apply cost budget multiplier if set
if [[ -n "${RUFLO_COST_BUDGET_MULTIPLIER:-}" ]]; then
    local _default_max="$max_agents"
    max_agents=$(awk -v d="$_default_max" -v m="${RUFLO_COST_BUDGET_MULTIPLIER}" \
        'BEGIN{v=int(d*m); print (v<1?1:(v>d?d:v))}' 2>/dev/null || echo "$max_agents")
fi

emit_event "ruflo.audit_start" "max_agents=$max_agents"

Semantic: Multiplier applied to security audit agent pool (default 4)


Step 4: Add Unit Tests

Location: scripts/sw-ruflo-adapter-test.sh (end of file, before final summary)

Add test section:

# ═══════════════════════════════════════════════════════════════════════════════
# Test N: RUFLO_COST_BUDGET_MULTIPLIER scaling
# ═══════════════════════════════════════════════════════════════════════════════
print_test_section "RUFLO_COST_BUDGET_MULTIPLIER application"

# Test N.1: Unset multiplier → no-op (backward compat)
RUFLO_COST_BUDGET_MULTIPLIER=""
# (Test by spawning mock and verifying agent count unchanged)

# Test N.2: Multiplier 2.0 → scale up to hard cap
RUFLO_COST_BUDGET_MULTIPLIER="2.0"
# (Verify agent count ≤ hard cap)

# Test N.3: Multiplier 0.5 → scale down, min 1
RUFLO_COST_BUDGET_MULTIPLIER="0.5"
# (Verify agent count ≥ 1, proportionally reduced)

# Test N.4: Multiplier 0 → enforces min 1
RUFLO_COST_BUDGET_MULTIPLIER="0"
# (Verify agent count = 1)

# Test N.5: Multiplier 3.0 → capped at hard limit
RUFLO_COST_BUDGET_MULTIPLIER="3.0"
# (Verify agent count ≤ original max_agents)

Detailed test implementations in Step 5 (Task Checklist)


Step 5: Run Test Suite

# Run ruflo-adapter-specific tests
./scripts/sw-ruflo-adapter-test.sh

# Run full test suite
npm test

Expected result: All tests pass, no regressions


7. Task Checklist

  • Task 1: Read scripts/lib/ruflo-adapter.sh lines 800-880 (understand ruflo_execute_review() structure)
  • Task 2: Apply multiplier code block after line 816 (local max_agents=...)
  • Task 3: Read scripts/lib/ruflo-adapter.sh lines 950-1000 (understand ruflo_execute_compound_quality() structure)
  • Task 4: Apply multiplier code block after line 961 (local cq_agents=...)
  • Task 5: Read scripts/lib/ruflo-adapter.sh lines 1060-1080 (understand ruflo_execute_audit() structure)
  • Task 6: Apply multiplier code block after line 1069 (local max_agents=...)
  • Task 7: Verify all 3 code blocks are identical (copy-paste correctly)
  • Task 8: Add test for unset multiplier (backward compat)
  • Task 9: Add test for 2.0 multiplier (scale up to cap)
  • Task 10: Add test for 0.5 multiplier (scale down, min 1)
  • Task 11: Add test for 0 multiplier (enforce min 1)
  • Task 12: Add test for 3.0 multiplier (enforce hard cap)
  • Task 13: Run ./scripts/sw-ruflo-adapter-test.sh — verify all tests pass
  • Task 14: Run npm test — verify no regressions
  • Task 15: Review diffs for correctness and style consistency

8. Testing Approach

Test Pyramid Breakdown

Total Target: ~18 tests across all layers

Layer Count Type Coverage
Unit Tests (70%) 13 tests sw-ruflo-adapter-test.sh section Multiplier formula, bounds, edge cases
Integration Tests (20%) 3 tests Config + function calls Load defaults + apply multiplier
E2E Tests (10%) 2 tests Full test suite npm test, sw-ruflo-adapter-test.sh

Coverage Targets

  • Multiplier application: 100% of lines added (awk invocation, fallback)
  • Edge cases: 100% (unset, 0, < 1, = 1, > 1, hard cap boundaries)
  • Error paths: awk failure → fallback to original max_agents

Critical Paths to Test

Happy Path: Multiplier 2.0 scales up to hard cap

RUFLO_MAX_AGENTS=4
RUFLO_COST_BUDGET_MULTIPLIER="2.0"
# Expected: max_agents = 8 (or capped if system limit < 8)

Scale Down: Multiplier 0.5 reduces proportionally

RUFLO_MAX_AGENTS=4
RUFLO_COST_BUDGET_MULTIPLIER="0.5"
# Expected: max_agents = 2

Backward Compat: Unset multiplier = no-op

RUFLO_MAX_AGENTS=4
RUFLO_COST_BUDGET_MULTIPLIER=""  (or unset)
# Expected: max_agents = 4 (unchanged)

Edge Case: Multiplier 0 enforces min 1

RUFLO_MAX_AGENTS=4
RUFLO_COST_BUDGET_MULTIPLIER="0"
# Expected: max_agents = 1

Edge Case: Multiplier 3.0 respects hard cap

RUFLO_MAX_AGENTS=4
RUFLO_COST_BUDGET_MULTIPLIER="3.0"
# Expected: max_agents = 4 (not 12, capped)

9. Component Diagram

┌─────────────────────────────────────────────────────────────────┐
│                       Pipeline Execution                         │
└──────────────────────┬──────────────────────────────────────────┘
                       │
                       ↓
        ┌──────────────────────────────┐
        │  ruflo_load_defaults()       │
        │  (loads config from JSON)    │
        │  ↓ exports:                  │
        │  RUFLO_COST_BUDGET_MULTIPLIER│
        └──────────────┬───────────────┘
                       │
        ┌──────────────┴──────────────────────────────────┐
        │                                                 │
        ↓                                                 ↓
┌──────────────────────────┐        ┌──────────────────────────────┐
│ ruflo_execute_review()   │        │ ruflo_execute_compound_quality()
│                          │        │
│ 1. max_agents = 4        │        │ 1. cq_agents = 3
│ 2. IF multiplier set:    │        │ 2. IF multiplier set:
│    apply via awk         │        │    apply via awk
│ 3. Spawn agents          │        │ 3. Spawn agents
└──────────────┬───────────┘        └──────────────┬───────────────┘
               │                                   │
               └───────────────────┬───────────────┘
                                   │
                        ┌──────────┴────────────┐
                        │                       │
                        ↓                       ↓
             ┌──────────────────────┐   ┌──────────────────────┐
             │ruflo_execute_audit() │   │ Hive-Mind Init       │
             │                      │   │ (spawns agents)      │
             │ 1. max_agents = 4    │   └──────────────────────┘
             │ 2. IF multiplier set:│
             │    apply via awk     │
             │ 3. Spawn agents      │
             └──────────────────────┘

All three functions use:
- Function-level env var (e.g., RUFLO_REVIEW_MAX_AGENTS)
- Global default (e.g., RUFLO_MAX_AGENTS)
- Cost multiplier (RUFLO_COST_BUDGET_MULTIPLIER)

10. Interface Contracts

Function Signatures (Unchanged)

# ruflo_execute_review(diff_content, artifact_file) -> int
# Usage: ruflo_execute_review "$diff" "/path/to/artifact"
# Returns: 0 = success, 1 = failure (fail-open)
# Environment:
#   IN:  RUFLO_REVIEW_MAX_AGENTS (override, optional)
#   IN:  RUFLO_MAX_AGENTS (default, optional)
#   IN:  RUFLO_COST_BUDGET_MULTIPLIER (NEW, scaling factor, optional)
#   OUT: (none) — writes to artifact_file

# ruflo_execute_compound_quality(diff_content, artifact_file) -> int
# Usage: ruflo_execute_compound_quality "$diff" "/path/to/artifact"
# Returns: 0 = success, 1 = failure (fail-open)
# Environment:
#   IN:  RUFLO_CQ_MAX_AGENTS (override, optional)
#   IN:  RUFLO_MAX_AGENTS (default, optional)
#   IN:  RUFLO_COST_BUDGET_MULTIPLIER (NEW, scaling factor, optional)
#   OUT: (none) — writes to artifact_file

# ruflo_execute_audit(diff_content, artifact_file) -> int
# Usage: ruflo_execute_audit "$diff" "/path/to/artifact"
# Returns: 0 = success, 1 = failure (fail-open)
# Environment:
#   IN:  RUFLO_AUDIT_MAX_AGENTS (override, optional)
#   IN:  RUFLO_MAX_AGENTS (default, optional)
#   IN:  RUFLO_COST_BUDGET_MULTIPLIER (NEW, scaling factor, optional)
#   OUT: (none) — writes to artifact_file

Multiplier Semantics

# Input: max_agents (numeric), multiplier (string/float)
# Formula: min(max_agents, max(1, floor(max_agents × multiplier)))
# 
# Constraints:
#  - min(result) = 1  (at least 1 agent spawned)
#  - max(result) = max_agents (never exceeds hard cap)
#  - unset multiplier = no-op (backward compatible)
#
# Examples:
#  max_agents=4, multiplier=2.0  → 4 × 2.0 = 8 → capped to 4
#  max_agents=4, multiplier=0.5  → 4 × 0.5 = 2
#  max_agents=4, multiplier=0    → 0 × 4   = 0 → enforced to 1
#  max_agents=4, multiplier=""   → unchanged (4)

11. Data Flow

Input Variables:
  ├─ RUFLO_REVIEW_MAX_AGENTS (env) ──┐
  ├─ RUFLO_CQ_MAX_AGENTS (env)        ├─→ Selected via ${VAR:-${DEFAULT:-4}}
  ├─ RUFLO_AUDIT_MAX_AGENTS (env)     │   (function-level override)
  ├─ RUFLO_MAX_AGENTS (env)           ├─→ Global default
  └─ RUFLO_COST_BUDGET_MULTIPLIER     ┤   (loaded by ruflo_load_defaults)
     (env)                             │

Processing:
  │
  ├─→ Assignment: local max_agents = "${VAR:-${DEFAULT:-4}}"
  │
  ├─→ Guard: if [[ -n "${RUFLO_COST_BUDGET_MULTIPLIER:-}" ]]; then
  │   │
  │   └─→ awk calculation:
  │       BEGIN { v = int(default_max × multiplier);
  │               print (v < 1 ? 1 : (v > default_max ? default_max : v)) }
  │   │
  │   └─→ Fallback: || echo "$max_agents"
  │   │
  │   └─→ Reassign: max_agents = $result
  │
  └─→ Usage: max_agents passed to hive-mind spawn

Output:
  ├─→ Adjusted max_agents (for agent spawning)
  └─→ Event: emit_event "ruflo.review_start" "max_agents=$max_agents"

12. Error Boundaries

Layer 1: Config Loading (ruflo_load_defaults)

# If .shipwright/defaults.json missing → no-op, returns 0
# If RUFLO_COST_BUDGET_MULTIPLIER missing from JSON → no-op
# If jq fails → true, $RUFLO_COST_BUDGET_MULTIPLIER left unset
# Result: Fail-open at config layer

Layer 2: Multiplier Application (in each ruflo_execute_*)

# If RUFLO_COST_BUDGET_MULTIPLIER unset → if guard skipped, no-op
# If awk fails/times out → fallback: || echo "$max_agents"
# If multiplier is malformed string → awk error, fallback to original
# Result: Fail-open, preserves original max_agents on error

Layer 3: Agent Spawning (hive-mind spawn)

# If max_agents is invalid → ruflo_with_timeout handles failure
# Pipeline continues (fail-open) — no agents spawned is non-fatal
# Result: Errors in spawning don't block subsequent stages

Layer 4: Pipeline

# All ruflo_execute_* functions return 0|1 (success|failure)
# Caller (e.g., review stage) handles failure → falls back to native logic
# Pipeline never blocked by ruflo failures
# Result: Entire ruflo subsystem is fail-open

Acceptance Criteria (Testable)

  1. Unset multiplier preserves behavior

    • RUFLO_COST_BUDGET_MULTIPLIER="" → agents unchanged from defaults
    • Test: assert max_agents == default_max_agents when multiplier unset
  2. Multiplier 2.0 scales up to hard cap

    • RUFLO_COST_BUDGET_MULTIPLIER="2.0" with max_agents=4 → result ≤ 4
    • Test: assert max_agents <= original_max_agents
  3. Multiplier 0.5 scales down proportionally

    • RUFLO_COST_BUDGET_MULTIPLIER="0.5" with max_agents=4 → result = 2
    • Test: assert max_agents == floor(4 * 0.5)
  4. Multiplier < 1 enforces minimum 1 agent

    • RUFLO_COST_BUDGET_MULTIPLIER="0" with any max_agents → result = 1
    • Test: assert max_agents >= 1
  5. Multiplier > 1 respects hard cap

    • RUFLO_COST_BUDGET_MULTIPLIER="3.0" with max_agents=4 → result = 4
    • Test: assert max_agents == original_max_agents
  6. All three functions apply multiplier identically

    • Test: verify ruflo_execute_review, ruflo_execute_compound_quality, ruflo_execute_audit all use same formula
  7. Test suite passes

    • ./scripts/sw-ruflo-adapter-test.sh → no failures
    • npm test → no regressions

Summary

This is a low-risk, 15-line change across 3 functions that wires a previously-dead config value into actual agent count scaling. The implementation is backward-compatible (unset multiplier = no-op), fail-open (errors fallback gracefully), and well-tested (5+ test scenarios covering happy path and edge cases).

The multiplier enables cost-aware deployment: 2.0 for performance testing, 0.5 for cost-constrained CI runs, and unset for standard operation.

Clone this wiki locally