641 lines
17 KiB
Markdown
641 lines
17 KiB
Markdown
# Feedback Loop Engine - Adaptive Workflow Coordination
|
||
|
||
## Overview
|
||
|
||
The Feedback Loop Engine enables bidirectional communication between agents, allowing downstream agents to notify upstream agents of constraints, inconsistencies, or issues discovered during execution. This creates an adaptive workflow that can self-correct and optimize based on real-world implementation findings.
|
||
|
||
---
|
||
|
||
## Core Concept
|
||
|
||
Traditional workflow: `Analyst → PM → Architect → Developer → QA` (one-way)
|
||
|
||
Feedback-enabled workflow:
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ │
|
||
▼ │
|
||
Analyst → PM → Architect → Developer → QA
|
||
▲ ▲ ▲ ▲
|
||
│ │ │ │
|
||
└────────┴────────┴──────────┘
|
||
(feedback notifications)
|
||
```
|
||
|
||
---
|
||
|
||
## Feedback Mechanisms
|
||
|
||
### 1. Constraint Backpropagation
|
||
|
||
**Scenario**: Developer discovers implementation constraint that affects architecture
|
||
|
||
```yaml
|
||
constraint_backpropagation:
|
||
trigger:
|
||
agent: developer
|
||
event: "implementation_constraint_discovered"
|
||
|
||
examples:
|
||
- constraint: "hosting doesn't support WebSockets"
|
||
requirement_id: "REQ-123"
|
||
affected_agents: ["architect", "pm"]
|
||
|
||
- constraint: "database performance insufficient"
|
||
requirement_id: "REQ-045"
|
||
affected_agents: ["architect"]
|
||
|
||
- constraint: "third-party API rate limit too restrictive"
|
||
requirement_id: "REQ-078"
|
||
affected_agents: ["architect", "pm"]
|
||
|
||
action:
|
||
- pause_workflow: true
|
||
- notify_agents: ["architect", "pm"]
|
||
- request_decision:
|
||
options:
|
||
- "revise_architecture"
|
||
- "change_hosting_provider"
|
||
- "revise_requirement"
|
||
- "accept_limitation"
|
||
- resume_on: "decision_made"
|
||
```
|
||
|
||
**Implementation Pattern**:
|
||
```javascript
|
||
// During developer agent execution
|
||
if (implementationConstraintFound) {
|
||
await feedbackLoop.notifyConstraint({
|
||
source: "developer",
|
||
constraint: {
|
||
type: "technical_limitation",
|
||
description: "WebSocket not supported by current hosting",
|
||
affected_requirement: "REQ-123",
|
||
severity: "blocking"
|
||
},
|
||
request: {
|
||
targets: ["architect", "pm"],
|
||
action_required: "architecture_revision_or_requirement_change",
|
||
options: [
|
||
{
|
||
option: "switch_to_polling",
|
||
impact: "performance_degradation",
|
||
effort: "low"
|
||
},
|
||
{
|
||
option: "change_hosting_provider",
|
||
impact: "deployment_complexity",
|
||
effort: "high"
|
||
},
|
||
{
|
||
option: "revise_realtime_requirement",
|
||
impact: "feature_reduction",
|
||
effort: "medium"
|
||
}
|
||
]
|
||
}
|
||
});
|
||
|
||
// Wait for resolution
|
||
const resolution = await feedbackLoop.waitForResolution("REQ-123");
|
||
|
||
// Continue with updated context
|
||
await implementWithResolution(resolution);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Validation Failure Callbacks
|
||
|
||
**Scenario**: Architect validates PM requirements and finds technical infeasibility
|
||
|
||
```yaml
|
||
validation_failure_callbacks:
|
||
architect_validates_pm:
|
||
trigger: "architect finds technical infeasibility"
|
||
|
||
examples:
|
||
- finding: "requirement X requires technology not in approved stack"
|
||
requirement_id: "REQ-056"
|
||
|
||
- finding: "performance requirement Y is not achievable with given constraints"
|
||
requirement_id: "REQ-089"
|
||
|
||
action:
|
||
- notify_pm:
|
||
message: "Technical validation failed for requirement X"
|
||
details: "architect's analysis and reasoning"
|
||
- request_revision: true
|
||
- preserve_context:
|
||
- "architect's technical analysis"
|
||
- "alternative approaches evaluated"
|
||
- re_execute:
|
||
- agent: "pm"
|
||
- step: 2
|
||
- context: "with_architect_feedback"
|
||
|
||
qa_validates_implementation:
|
||
trigger: "qa finds missing functionality"
|
||
|
||
examples:
|
||
- finding: "acceptance criteria not met"
|
||
requirement_id: "REQ-012"
|
||
test_case: "TC-045"
|
||
|
||
- finding: "non-functional requirement violated"
|
||
requirement_id: "REQ-067"
|
||
metric: "response_time > 2s (target: 500ms)"
|
||
|
||
action:
|
||
- trace_to_requirement: true
|
||
- identify_gap:
|
||
compare:
|
||
- "pm.requirements"
|
||
- "developer.implementation"
|
||
- escalate_to: ["pm", "developer"]
|
||
- decision:
|
||
options:
|
||
- "add_missing_requirement"
|
||
- "fix_implementation"
|
||
- "mark_as_limitation"
|
||
- "revise_acceptance_criteria"
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Inconsistency Detection
|
||
|
||
**Scenario**: Multiple agents produce conflicting outputs
|
||
|
||
```yaml
|
||
inconsistency_detection:
|
||
ux_architect_mismatch:
|
||
detection:
|
||
compare:
|
||
- "ux_expert.ui_spec.design_system.breakpoints"
|
||
- "architect.system_architecture.responsive_strategy"
|
||
|
||
conflicts:
|
||
- ux_expert: "mobile_first with 320px base"
|
||
architect: "desktop_primary with 1024px base"
|
||
severity: "high"
|
||
|
||
resolution:
|
||
- notify_both: ["ux_expert", "architect"]
|
||
- request_alignment:
|
||
method: "consensus_meeting"
|
||
facilitator: "orchestrator"
|
||
- update_both_artifacts: true
|
||
|
||
pm_architect_constraint_mismatch:
|
||
detection:
|
||
compare:
|
||
- "pm.prd.technical_constraints"
|
||
- "architect.system_architecture.selected_technologies"
|
||
|
||
conflicts:
|
||
- pm: "must support IE11"
|
||
architect: "selected React 18 (no IE11 support)"
|
||
severity: "critical"
|
||
|
||
resolution:
|
||
- pause_workflow: true
|
||
- notify_agents: ["pm", "architect"]
|
||
- request_resolution:
|
||
options:
|
||
- "drop_ie11_requirement"
|
||
- "use_polyfills"
|
||
- "downgrade_react_version"
|
||
- "accept_dual_implementation"
|
||
- resume_on: "consensus_reached"
|
||
```
|
||
|
||
---
|
||
|
||
### 4. Quality Gate Feedback
|
||
|
||
**Scenario**: Quality gate fails and needs upstream attention
|
||
|
||
```yaml
|
||
quality_gate_feedback:
|
||
accessibility_failure:
|
||
gate: "WCAG_AA_compliance"
|
||
threshold: "zero_violations"
|
||
actual: "12_violations"
|
||
|
||
action:
|
||
- notify_ux_expert:
|
||
message: "Accessibility violations detected"
|
||
details: "12 WCAG AA violations in components"
|
||
violations: ["color_contrast", "keyboard_navigation", "aria_labels"]
|
||
- request_fix:
|
||
target: "ux_expert"
|
||
artifact: "ui_spec"
|
||
specific_issues: [...violations]
|
||
- block_developer_step: true
|
||
- resume_on: "ux_spec_revised"
|
||
|
||
performance_failure:
|
||
gate: "performance_budget"
|
||
threshold: "lighthouse_score >= 90"
|
||
actual: "lighthouse_score = 67"
|
||
|
||
action:
|
||
- trace_root_cause:
|
||
- "bundle_size: 2.5MB (target: 500KB)"
|
||
- "unoptimized_images: 15 files"
|
||
- "blocking_scripts: 8 files"
|
||
- notify_agents: ["architect", "developer", "ux_expert"]
|
||
- request_optimization:
|
||
- architect: "review_technology_choices"
|
||
- developer: "implement_code_splitting"
|
||
- ux_expert: "optimize_image_assets"
|
||
- re_validate_on: "all_fixes_applied"
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation Architecture
|
||
|
||
### Feedback Loop State Machine
|
||
|
||
```yaml
|
||
feedback_loop_states:
|
||
IDLE:
|
||
description: "No active feedback loops"
|
||
transitions:
|
||
- event: "feedback_triggered"
|
||
next_state: "NOTIFYING"
|
||
|
||
NOTIFYING:
|
||
description: "Sending notifications to target agents"
|
||
actions:
|
||
- create_feedback_record
|
||
- send_notifications
|
||
- update_context_bus
|
||
transitions:
|
||
- event: "notifications_sent"
|
||
next_state: "WAITING_RESPONSE"
|
||
|
||
WAITING_RESPONSE:
|
||
description: "Waiting for agent acknowledgment and action"
|
||
timeout: "10_minutes"
|
||
actions:
|
||
- poll_for_acknowledgment
|
||
- track_response_time
|
||
transitions:
|
||
- event: "response_received"
|
||
next_state: "RESOLVING"
|
||
- event: "timeout"
|
||
next_state: "ESCALATING"
|
||
|
||
RESOLVING:
|
||
description: "Agents are working on resolution"
|
||
actions:
|
||
- monitor_progress
|
||
- coordinate_updates
|
||
- validate_resolution
|
||
transitions:
|
||
- event: "resolution_complete"
|
||
next_state: "VALIDATING"
|
||
- event: "resolution_failed"
|
||
next_state: "ESCALATING"
|
||
|
||
VALIDATING:
|
||
description: "Validating the resolution"
|
||
actions:
|
||
- run_validation_checks
|
||
- verify_consistency
|
||
- update_artifacts
|
||
transitions:
|
||
- event: "validation_passed"
|
||
next_state: "RESOLVED"
|
||
- event: "validation_failed"
|
||
next_state: "ESCALATING"
|
||
|
||
RESOLVED:
|
||
description: "Feedback loop successfully resolved"
|
||
actions:
|
||
- update_context
|
||
- log_resolution
|
||
- resume_workflow
|
||
transitions:
|
||
- event: "new_feedback"
|
||
next_state: "NOTIFYING"
|
||
- event: "workflow_complete"
|
||
next_state: "IDLE"
|
||
|
||
ESCALATING:
|
||
description: "Escalating to human or system administrator"
|
||
actions:
|
||
- create_escalation_ticket
|
||
- notify_administrators
|
||
- pause_workflow
|
||
transitions:
|
||
- event: "manual_resolution"
|
||
next_state: "RESOLVED"
|
||
```
|
||
|
||
---
|
||
|
||
### Context Bus Integration
|
||
|
||
```javascript
|
||
// Feedback loop uses context bus for coordination
|
||
class FeedbackLoopEngine {
|
||
constructor(contextBus) {
|
||
this.contextBus = contextBus;
|
||
this.activeLoops = new Map();
|
||
|
||
// Subscribe to agent completion events
|
||
this.contextBus.subscribe('agent_contexts.*.status', (newStatus, oldStatus, path) => {
|
||
if (newStatus === 'completed') {
|
||
this.onAgentCompleted(path);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Trigger feedback loop
|
||
*/
|
||
async trigger(feedbackConfig) {
|
||
const loopId = `loop-${Date.now()}`;
|
||
|
||
// Create feedback loop record
|
||
const loop = {
|
||
id: loopId,
|
||
triggered_at: new Date().toISOString(),
|
||
source_agent: feedbackConfig.source,
|
||
target_agents: feedbackConfig.targets,
|
||
issue_type: feedbackConfig.type,
|
||
description: feedbackConfig.description,
|
||
severity: feedbackConfig.severity,
|
||
status: 'pending',
|
||
state: 'NOTIFYING'
|
||
};
|
||
|
||
// Store in context
|
||
this.contextBus.push('feedback_loops', loop);
|
||
this.activeLoops.set(loopId, loop);
|
||
|
||
// Notify target agents
|
||
await this.notifyAgents(loop);
|
||
|
||
return loopId;
|
||
}
|
||
|
||
/**
|
||
* Notify target agents
|
||
*/
|
||
async notifyAgents(loop) {
|
||
for (const targetAgent of loop.target_agents) {
|
||
const notification = {
|
||
from_agent: loop.source_agent,
|
||
type: loop.issue_type,
|
||
message: loop.description,
|
||
severity: loop.severity,
|
||
resolved: false,
|
||
loop_id: loop.id
|
||
};
|
||
|
||
this.contextBus.push(
|
||
`agent_contexts.${targetAgent}.feedback_received`,
|
||
notification
|
||
);
|
||
}
|
||
|
||
// Update state
|
||
loop.state = 'WAITING_RESPONSE';
|
||
this.contextBus.update(`feedback_loops.${loop.id}`, { state: 'WAITING_RESPONSE' });
|
||
}
|
||
|
||
/**
|
||
* Wait for resolution
|
||
*/
|
||
async waitForResolution(loopId, timeout = 600000) {
|
||
return new Promise((resolve, reject) => {
|
||
const startTime = Date.now();
|
||
|
||
const checkInterval = setInterval(() => {
|
||
const loop = this.activeLoops.get(loopId);
|
||
|
||
if (!loop) {
|
||
clearInterval(checkInterval);
|
||
reject(new Error(`Loop not found: ${loopId}`));
|
||
return;
|
||
}
|
||
|
||
if (loop.status === 'resolved') {
|
||
clearInterval(checkInterval);
|
||
resolve(loop.resolution);
|
||
return;
|
||
}
|
||
|
||
if (Date.now() - startTime > timeout) {
|
||
clearInterval(checkInterval);
|
||
this.escalate(loopId, 'timeout');
|
||
reject(new Error(`Feedback loop timeout: ${loopId}`));
|
||
}
|
||
}, 1000);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Resolve feedback loop
|
||
*/
|
||
async resolve(loopId, resolution) {
|
||
const loop = this.activeLoops.get(loopId);
|
||
|
||
if (!loop) {
|
||
throw new Error(`Loop not found: ${loopId}`);
|
||
}
|
||
|
||
loop.status = 'resolved';
|
||
loop.state = 'RESOLVED';
|
||
loop.resolution = resolution;
|
||
loop.resolved_at = new Date().toISOString();
|
||
|
||
// Update context
|
||
this.contextBus.update(`feedback_loops.${loop.id}`, {
|
||
status: 'resolved',
|
||
state: 'RESOLVED',
|
||
resolution: resolution,
|
||
resolved_at: loop.resolved_at
|
||
});
|
||
|
||
// Mark notifications as resolved
|
||
for (const targetAgent of loop.target_agents) {
|
||
const feedbacks = this.contextBus.get(`agent_contexts.${targetAgent}.feedback_received`) || [];
|
||
for (const feedback of feedbacks) {
|
||
if (feedback.loop_id === loopId) {
|
||
feedback.resolved = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Remove from active loops
|
||
this.activeLoops.delete(loopId);
|
||
|
||
return resolution;
|
||
}
|
||
|
||
/**
|
||
* Escalate unresolved loop
|
||
*/
|
||
async escalate(loopId, reason) {
|
||
const loop = this.activeLoops.get(loopId);
|
||
|
||
if (!loop) return;
|
||
|
||
loop.status = 'escalated';
|
||
loop.state = 'ESCALATING';
|
||
loop.escalation_reason = reason;
|
||
|
||
this.contextBus.update(`feedback_loops.${loop.id}`, {
|
||
status: 'escalated',
|
||
state: 'ESCALATING',
|
||
escalation_reason: reason
|
||
});
|
||
|
||
// Pause workflow
|
||
this.contextBus.set('workflow_state.paused', true);
|
||
this.contextBus.set('workflow_state.pause_reason', `Feedback loop escalation: ${loopId}`);
|
||
|
||
console.error(`\n⚠️ Feedback loop escalated: ${loopId}`);
|
||
console.error(` Reason: ${reason}`);
|
||
console.error(` Issue: ${loop.description}`);
|
||
console.error(` Manual intervention required.\n`);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Usage Examples
|
||
|
||
### Example 1: Developer finds WebSocket constraint
|
||
|
||
```javascript
|
||
// In developer agent
|
||
const feedbackLoop = new FeedbackLoopEngine(contextBus);
|
||
|
||
const loopId = await feedbackLoop.trigger({
|
||
source: 'developer',
|
||
targets: ['architect', 'pm'],
|
||
type: 'constraint_violation',
|
||
severity: 'blocking',
|
||
description: 'WebSocket not supported by current hosting (Vercel)',
|
||
details: {
|
||
requirement_id: 'REQ-123',
|
||
requirement_text: 'Real-time collaboration with WebSocket',
|
||
constraint: 'Vercel serverless functions do not support persistent WebSocket connections',
|
||
options: [
|
||
{ option: 'Use polling', effort: 'low', impact: 'performance_degradation' },
|
||
{ option: 'Switch to AWS with EC2', effort: 'high', impact: 'deployment_change' },
|
||
{ option: 'Use third-party service (Pusher)', effort: 'medium', impact: 'cost_increase' }
|
||
]
|
||
}
|
||
});
|
||
|
||
// Wait for architect and PM to decide
|
||
const resolution = await feedbackLoop.waitForResolution(loopId);
|
||
|
||
console.log(`Resolution: ${resolution.decision}`);
|
||
// Implement based on resolution
|
||
```
|
||
|
||
### Example 2: Architect finds performance infeasibility
|
||
|
||
```javascript
|
||
// In architect agent
|
||
const feedbackLoop = new FeedbackLoopEngine(contextBus);
|
||
|
||
const loopId = await feedbackLoop.trigger({
|
||
source: 'architect',
|
||
targets: ['pm'],
|
||
type: 'technical_infeasibility',
|
||
severity: 'high',
|
||
description: 'Performance requirement not achievable with current constraints',
|
||
details: {
|
||
requirement_id: 'REQ-089',
|
||
requirement_text: 'Page load time < 500ms',
|
||
analysis: 'With 50MB of data to load and 3G mobile network (750Kbps), minimum load time is 2.5 seconds',
|
||
recommendation: 'Revise requirement to < 2 seconds OR reduce initial data load'
|
||
}
|
||
});
|
||
|
||
const resolution = await feedbackLoop.waitForResolution(loopId);
|
||
```
|
||
|
||
### Example 3: QA finds missing functionality
|
||
|
||
```javascript
|
||
// In QA agent
|
||
const feedbackLoop = new FeedbackLoopEngine(contextBus);
|
||
|
||
const loopId = await feedbackLoop.trigger({
|
||
source: 'qa',
|
||
targets: ['pm', 'developer'],
|
||
type: 'missing_requirement',
|
||
severity: 'medium',
|
||
description: 'User story acceptance criteria not met',
|
||
details: {
|
||
user_story_id: 'US-045',
|
||
acceptance_criteria: 'User can export data as CSV',
|
||
actual_implementation: 'Only JSON export is implemented',
|
||
gap: 'CSV export functionality missing'
|
||
}
|
||
});
|
||
|
||
const resolution = await feedbackLoop.waitForResolution(loopId);
|
||
```
|
||
|
||
---
|
||
|
||
## Benefits
|
||
|
||
1. **Adaptive Workflows**: Workflows can self-correct based on real-world findings
|
||
2. **Reduced Rework**: Issues discovered early through validation callbacks
|
||
3. **Better Alignment**: Inconsistencies detected and resolved automatically
|
||
4. **Transparent Decision-Making**: All feedback and resolutions logged in context
|
||
5. **Improved Quality**: Multiple validation passes ensure consistency
|
||
|
||
---
|
||
|
||
## Integration with Parallel Execution
|
||
|
||
Feedback loops work seamlessly with parallel execution:
|
||
|
||
```yaml
|
||
parallel_execution_with_feedback:
|
||
# Architect and UX Expert run in parallel
|
||
- group: design_and_architecture
|
||
parallel: true
|
||
agents: [ux-expert, architect]
|
||
|
||
# On completion, check for inconsistencies
|
||
- on_group_complete:
|
||
run: consistency_check
|
||
compare:
|
||
- ux-expert.outputs.design_system
|
||
- architect.outputs.technology_stack
|
||
|
||
if: inconsistencies_found
|
||
then:
|
||
trigger_feedback_loop:
|
||
source: orchestrator
|
||
targets: [ux-expert, architect]
|
||
type: inconsistency
|
||
|
||
# Wait for resolution before proceeding
|
||
- next_group: implementation
|
||
depends_on: [design_and_architecture, feedback_loops_resolved]
|
||
```
|
||
|
||
---
|
||
|
||
## Conclusion
|
||
|
||
The Feedback Loop Engine transforms BMAD-SPEC-KIT from a one-directional pipeline into an adaptive, self-correcting system that can handle real-world complexity and constraints discovered during execution.
|