-
Notifications
You must be signed in to change notification settings - Fork 0
Pipeline Plan 414
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)
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)
| 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
- Code Changes: Multiplier applied to all 3 functions (
ruflo_execute_review,ruflo_execute_compound_quality,ruflo_execute_audit) - Backward Compatibility: Unset
RUFLO_COST_BUDGET_MULTIPLIERbehaves 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
< 1enforces min 1 agent; multiplier> 1respects hard cap - Unit Tests Pass: All new tests in
sw-ruflo-adapter-test.shpass (unset, 2.0, 0.5, 0, 3.0) - Full Test Suite Passes:
npm testandscripts/sw-ruflo-adapter-test.shpass 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)
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")
fiPros:
- 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
_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
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_maxPros:
- 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
| 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.
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
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)
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)
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)
# Run ruflo-adapter-specific tests
./scripts/sw-ruflo-adapter-test.sh
# Run full test suite
npm testExpected result: All tests pass, no regressions
- Task 1: Read
scripts/lib/ruflo-adapter.shlines 800-880 (understandruflo_execute_review()structure) - Task 2: Apply multiplier code block after line 816 (
local max_agents=...) - Task 3: Read
scripts/lib/ruflo-adapter.shlines 950-1000 (understandruflo_execute_compound_quality()structure) - Task 4: Apply multiplier code block after line 961 (
local cq_agents=...) - Task 5: Read
scripts/lib/ruflo-adapter.shlines 1060-1080 (understandruflo_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
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 |
- 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
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 = 2Backward 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 = 1Edge Case: Multiplier 3.0 respects hard cap
RUFLO_MAX_AGENTS=4
RUFLO_COST_BUDGET_MULTIPLIER="3.0"
# Expected: max_agents = 4 (not 12, capped)┌─────────────────────────────────────────────────────────────────┐
│ 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)
# 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# 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)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"
# 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# 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# 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# 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-
✅ Unset multiplier preserves behavior
-
RUFLO_COST_BUDGET_MULTIPLIER=""→ agents unchanged from defaults - Test: assert
max_agents == default_max_agentswhen multiplier unset
-
-
✅ Multiplier 2.0 scales up to hard cap
-
RUFLO_COST_BUDGET_MULTIPLIER="2.0"withmax_agents=4→ result ≤ 4 - Test: assert
max_agents <= original_max_agents
-
-
✅ Multiplier 0.5 scales down proportionally
-
RUFLO_COST_BUDGET_MULTIPLIER="0.5"withmax_agents=4→ result = 2 - Test: assert
max_agents == floor(4 * 0.5)
-
-
✅ Multiplier < 1 enforces minimum 1 agent
-
RUFLO_COST_BUDGET_MULTIPLIER="0"with anymax_agents→ result = 1 - Test: assert
max_agents >= 1
-
-
✅ Multiplier > 1 respects hard cap
-
RUFLO_COST_BUDGET_MULTIPLIER="3.0"withmax_agents=4→ result = 4 - Test: assert
max_agents == original_max_agents
-
-
✅ All three functions apply multiplier identically
- Test: verify
ruflo_execute_review,ruflo_execute_compound_quality,ruflo_execute_auditall use same formula
- Test: verify
-
✅ Test suite passes
-
./scripts/sw-ruflo-adapter-test.sh→ no failures -
npm test→ no regressions
-
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.