@pjmendonca/devflow 1.13.1 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/agent.md +1 -1
- package/.claude/commands/bugfix.md +21 -0
- package/.claude/commands/checkpoint.md +0 -1
- package/.claude/commands/collab.md +0 -1
- package/.claude/commands/costs.md +88 -18
- package/.claude/commands/devflow.md +26 -0
- package/.claude/commands/handoff.md +0 -1
- package/.claude/commands/memory.md +0 -1
- package/.claude/commands/pair.md +0 -1
- package/.claude/commands/review.md +27 -0
- package/.claude/commands/route.md +0 -1
- package/.claude/commands/swarm.md +0 -1
- package/.claude/commands/validate.md +55 -0
- package/.claude/hooks/session-notification.sh +44 -0
- package/.claude/hooks/session-startup.sh +427 -0
- package/.claude/hooks/session-stop.sh +38 -0
- package/.claude/hooks/session_tracker.py +272 -0
- package/.claude/settings.json +38 -0
- package/.claude/skills/costs/SKILL.md +156 -0
- package/.claude/skills/validate/SKILL.md +101 -0
- package/CHANGELOG.md +254 -0
- package/README.md +207 -10
- package/bin/devflow-install.js +2 -1
- package/bin/devflow.js +5 -2
- package/lib/constants.js +0 -1
- package/lib/exec-python.js +1 -1
- package/package.json +1 -2
- package/tooling/.automation/.checkpoint_lock +1 -0
- package/tooling/.automation/agents/architect.md +19 -0
- package/tooling/.automation/agents/ba.md +19 -0
- package/tooling/.automation/agents/maintainer.md +19 -0
- package/tooling/.automation/agents/pm.md +19 -0
- package/tooling/.automation/agents/reviewer.md +1 -1
- package/tooling/.automation/agents/writer.md +19 -0
- package/tooling/.automation/benchmarks/benchmark_20251230_100119.json +314 -0
- package/tooling/.automation/benchmarks/benchmark_20251230_100216.json +314 -0
- package/tooling/.automation/costs/config.json +31 -0
- package/tooling/.automation/costs/sessions/2025-12-29_20251229_164128.json +22 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +707 -1
- package/tooling/.automation/memory/knowledge/kg_test-story.json +3273 -2
- package/tooling/.automation/memory/shared/shared_integration-test.json +181 -1
- package/tooling/.automation/memory/shared/shared_test-story.json +721 -1
- package/tooling/.automation/memory/shared/shared_test.json +1254 -0
- package/tooling/.automation/memory/shared/shared_validation-check.json +227 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +5 -5
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +23 -5
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +24 -6
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +4 -4
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +4 -4
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +4 -4
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +18 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +19 -1
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +18 -0
- package/tooling/.automation/overrides/templates/dev/user-advocate.yaml +54 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +4 -4
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +4 -4
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +4 -4
- package/tooling/.automation/overrides/templates/maintainer/reliability-engineer.yaml +55 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +4 -4
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +3 -3
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +4 -4
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +18 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +18 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +2 -2
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +3 -3
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +5 -5
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +4 -4
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +5 -5
- package/tooling/.automation/validation/history/2025-12-29_val_002a28c1.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_01273bb1.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_03369914.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_07a449ba.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_0df1f0a2.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_10ff3d34.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_110771d7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_13f3a7f9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_17ba9d21.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_22247089.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_227ea6a4.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2335d5ae.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_246824bb.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_28b4b9cd.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2abd12cc.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2c801b2f.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2c8cfa8e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2ce76eb0.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_30351948.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_30eb7229.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_34df0e77.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_376e4d6a.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_3a4e8a1a.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_3b77a628.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_3ea4e1cf.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_44aacdb4.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_457ddfa8.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_45af6238.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_4735dba1.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_486b203c.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_49dc56cd.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_4d863d6d.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_5149a808.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_52e0bb43.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_585d6319.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_5b2d859a.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_635a7081.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_64df4905.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_70634cee.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_714553f9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_7f7bfdbf.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_7faad91d.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_81821f8f.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8249f3c9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8422b50f.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8446c134.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_879f4e26.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8b6d5bd7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8c5cd787.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_91d20bc7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_958a12b7.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_95d91108.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_980dbb74.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_9e40c79b.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_9f499b7c.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_9f7c3b57.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_a30d5bd4.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_a6eb09c7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_a86f7b83.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_ad5347e1.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_b0a5a993.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_bcb0192e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_bf3c9aaa.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_c461ff88.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_c4f4e258.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_c7f0fa6d.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_c911b0e6.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_cc581964.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_cdd5a33b.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_cfd42495.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d1c7a4ee.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d2280d0e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d2a6ff69.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d8c53ab2.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d9c1247a.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d9d58569.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_dabb4fd9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_dd8fe359.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_decdffc9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_e3a95476.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_e776dfca.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_ea70969f.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_ef41ea95.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_f384f9b1.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_f8adc38c.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_fa40b69e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_fc538d54.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_fe814665.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_ffea4b12.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_02d001e5.json +59 -0
- package/tooling/.automation/validation/history/2025-12-30_val_0b8966dc.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_15455fbf.json +59 -0
- package/tooling/.automation/validation/history/2025-12-30_val_157e34b9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_28d1d933.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_3442a52c.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_37f1ce1e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_4f1d8a93.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_56ff1de3.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_664fd4e2.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_66afb0a7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_7634663c.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_8ea830c3.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_998957c2.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_a52177db.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_a5b65a63.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_ae391d0e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_c7895339.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_ca416593.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_cee19422.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_ddd4f4e6.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_f2e1394b.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_f4a7fa06.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_ffea3369.json +32 -0
- package/tooling/.automation/validation-config.yaml +103 -0
- package/tooling/completions/DevflowCompletion.ps1 +21 -21
- package/tooling/completions/_run-story +3 -3
- package/tooling/completions/run-story-completion.bash +8 -8
- package/tooling/docs/DOC-STANDARD.md +14 -14
- package/tooling/docs/templates/migration-spec.md +4 -4
- package/tooling/scripts/context_checkpoint.py +5 -15
- package/tooling/scripts/cost_dashboard.py +610 -13
- package/tooling/scripts/create-persona.py +1 -12
- package/tooling/scripts/create-persona.sh +44 -44
- package/tooling/scripts/lib/__init__.py +12 -1
- package/tooling/scripts/lib/agent_handoff.py +11 -2
- package/tooling/scripts/lib/agent_router.py +31 -10
- package/tooling/scripts/lib/colors.py +106 -0
- package/tooling/scripts/lib/context_monitor.py +766 -0
- package/tooling/scripts/lib/cost_config.py +229 -10
- package/tooling/scripts/lib/cost_display.py +20 -45
- package/tooling/scripts/lib/cost_tracker.py +462 -15
- package/tooling/scripts/lib/currency_converter.py +28 -5
- package/tooling/scripts/lib/pair_programming.py +102 -3
- package/tooling/scripts/lib/personality_system.py +949 -0
- package/tooling/scripts/lib/platform.py +55 -0
- package/tooling/scripts/lib/shared_memory.py +9 -3
- package/tooling/scripts/lib/swarm_orchestrator.py +514 -75
- package/tooling/scripts/lib/validation_loop.py +1014 -0
- package/tooling/scripts/memory_summarize.py +9 -2
- package/tooling/scripts/new-doc.py +2 -9
- package/tooling/scripts/personalize_agent.py +1 -12
- package/tooling/scripts/rollback-migration.sh +60 -60
- package/tooling/scripts/run-collab.ps1 +16 -16
- package/tooling/scripts/run-collab.py +88 -53
- package/tooling/scripts/run-collab.sh +4 -4
- package/tooling/scripts/run-story.py +278 -20
- package/tooling/scripts/run-story.sh +3 -3
- package/tooling/scripts/setup-checkpoint-service.py +2 -9
- package/tooling/scripts/tech-debt-tracker.py +1 -12
- package/tooling/scripts/test_adversarial_swarm.py +452 -0
- package/tooling/scripts/update_version.py +48 -2
- package/tooling/scripts/validate-overrides.py +1 -10
- package/tooling/scripts/validate-overrides.sh +40 -40
- package/tooling/scripts/validate_loop.py +162 -0
- package/tooling/scripts/validate_setup.py +2 -30
- package/.claude/skills/init/SKILL.md +0 -496
- package/bin/devflow-init.js +0 -10
- package/tooling/scripts/init-project-workflow.ps1 +0 -651
- package/tooling/scripts/init-project-workflow.py +0 -70
- package/tooling/scripts/init-project-workflow.sh +0 -746
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Swarm Orchestrator - Multi-Agent Collaboration System
|
|
3
|
+
Swarm Orchestrator - Adversarial Multi-Agent Collaboration System
|
|
4
4
|
|
|
5
|
-
Orchestrates multiple agents
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- Parallel agent execution
|
|
9
|
-
- Conflict resolution
|
|
10
|
-
- Convergence detection
|
|
5
|
+
Orchestrates multiple agents in ADVERSARIAL debate mode where agents
|
|
6
|
+
take opposing stances and challenge each other's designs. This creates
|
|
7
|
+
robust solutions through competitive pressure.
|
|
11
8
|
|
|
12
9
|
Features:
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
10
|
+
- Adversarial personas: Agents are assigned opposing viewpoints
|
|
11
|
+
- Dynamic personality selection: Task analysis determines best persona mix
|
|
12
|
+
- Convergence detection: Hybrid token/round limits with stability checks
|
|
13
|
+
- Personality handoff: Debate summary passed to implementing agent
|
|
14
|
+
- Cross-challenge rounds: Agents directly challenge each other
|
|
15
|
+
|
|
16
|
+
The adversarial approach:
|
|
17
|
+
1. General agent (e.g., DEV) receives task
|
|
18
|
+
2. Spawns sub-agents with opposing personas (e.g., Security vs Velocity)
|
|
19
|
+
3. Agents debate in rounds, challenging each other's positions
|
|
20
|
+
4. Convergence is forced after budget/round limits
|
|
21
|
+
5. Synthesized result returned to implementing agent
|
|
18
22
|
|
|
19
23
|
Usage:
|
|
20
24
|
from lib.swarm_orchestrator import SwarmOrchestrator, SwarmConfig
|
|
@@ -23,13 +27,13 @@ Usage:
|
|
|
23
27
|
result = orchestrator.run_swarm(
|
|
24
28
|
agents=["ARCHITECT", "DEV", "REVIEWER"],
|
|
25
29
|
task="Design and implement user authentication",
|
|
26
|
-
max_iterations=
|
|
30
|
+
max_iterations=5
|
|
27
31
|
)
|
|
32
|
+
# result includes PersonalityHandoff with debate summary
|
|
28
33
|
"""
|
|
29
34
|
|
|
30
35
|
import re
|
|
31
36
|
import subprocess
|
|
32
|
-
import sys
|
|
33
37
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
34
38
|
from dataclasses import dataclass, field
|
|
35
39
|
from datetime import datetime
|
|
@@ -39,17 +43,87 @@ from typing import Optional
|
|
|
39
43
|
|
|
40
44
|
# Import dependencies
|
|
41
45
|
try:
|
|
46
|
+
from platform import IS_WINDOWS
|
|
47
|
+
|
|
42
48
|
from agent_handoff import HandoffGenerator
|
|
43
49
|
from agent_router import AgentRouter
|
|
50
|
+
from personality_system import (
|
|
51
|
+
ConvergenceDetector,
|
|
52
|
+
DebatePosition,
|
|
53
|
+
PersonalityHandoff,
|
|
54
|
+
PersonalityProfile,
|
|
55
|
+
PersonalitySelector,
|
|
56
|
+
extract_arguments_from_response,
|
|
57
|
+
)
|
|
44
58
|
from shared_memory import get_knowledge_graph, get_shared_memory
|
|
45
59
|
except ImportError:
|
|
46
60
|
from lib.agent_handoff import HandoffGenerator
|
|
47
61
|
from lib.agent_router import AgentRouter
|
|
62
|
+
from lib.personality_system import (
|
|
63
|
+
ConvergenceDetector,
|
|
64
|
+
DebatePosition,
|
|
65
|
+
PersonalityHandoff,
|
|
66
|
+
PersonalityProfile,
|
|
67
|
+
PersonalitySelector,
|
|
68
|
+
extract_arguments_from_response,
|
|
69
|
+
)
|
|
70
|
+
from lib.platform import IS_WINDOWS
|
|
48
71
|
from lib.shared_memory import get_knowledge_graph, get_shared_memory
|
|
49
72
|
|
|
73
|
+
# Try to import validation loop
|
|
74
|
+
try:
|
|
75
|
+
from validation_loop import (
|
|
76
|
+
INTER_PHASE_GATES,
|
|
77
|
+
LoopContext,
|
|
78
|
+
ValidationLoop,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
HAS_VALIDATION = True
|
|
82
|
+
except ImportError:
|
|
83
|
+
try:
|
|
84
|
+
from lib.validation_loop import (
|
|
85
|
+
INTER_PHASE_GATES,
|
|
86
|
+
LoopContext,
|
|
87
|
+
ValidationLoop,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
HAS_VALIDATION = True
|
|
91
|
+
except ImportError:
|
|
92
|
+
HAS_VALIDATION = False
|
|
93
|
+
|
|
50
94
|
|
|
51
95
|
PROJECT_ROOT = Path(__file__).parent.parent.parent.parent
|
|
52
|
-
CLAUDE_CLI = "claude.cmd" if
|
|
96
|
+
CLAUDE_CLI = "claude.cmd" if IS_WINDOWS else "claude"
|
|
97
|
+
|
|
98
|
+
# Security: Maximum prompt length to prevent resource exhaustion
|
|
99
|
+
MAX_PROMPT_LENGTH = 500_000 # ~500KB
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _sanitize_prompt(prompt: str) -> str:
|
|
103
|
+
"""Sanitize prompt for safe subprocess execution.
|
|
104
|
+
|
|
105
|
+
- Removes null bytes and control characters (except newlines/tabs)
|
|
106
|
+
- Truncates to maximum length
|
|
107
|
+
- Ensures valid UTF-8
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
prompt: Raw prompt string
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Sanitized prompt safe for subprocess
|
|
114
|
+
"""
|
|
115
|
+
if not prompt:
|
|
116
|
+
return ""
|
|
117
|
+
|
|
118
|
+
# Remove null bytes and control characters (keep newlines, tabs, and printable chars)
|
|
119
|
+
# Keep: \n (10), \t (9), \r (13), and all chars >= 32 (printable ASCII + UTF-8)
|
|
120
|
+
sanitized = "".join(char for char in prompt if char in "\n\t\r" or ord(char) >= 32)
|
|
121
|
+
|
|
122
|
+
# Truncate if too long
|
|
123
|
+
if len(sanitized) > MAX_PROMPT_LENGTH:
|
|
124
|
+
sanitized = sanitized[:MAX_PROMPT_LENGTH] + "\n[TRUNCATED]"
|
|
125
|
+
|
|
126
|
+
return sanitized
|
|
53
127
|
|
|
54
128
|
|
|
55
129
|
class SwarmState(Enum):
|
|
@@ -76,7 +150,7 @@ class ConsensusType(Enum):
|
|
|
76
150
|
|
|
77
151
|
@dataclass
|
|
78
152
|
class AgentResponse:
|
|
79
|
-
"""Response from an agent."""
|
|
153
|
+
"""Response from an agent in adversarial debate."""
|
|
80
154
|
|
|
81
155
|
agent: str
|
|
82
156
|
model: str
|
|
@@ -89,6 +163,11 @@ class AgentResponse:
|
|
|
89
163
|
approvals: list[str] = field(default_factory=list)
|
|
90
164
|
suggestions: list[str] = field(default_factory=list)
|
|
91
165
|
vote: Optional[str] = None # approve, reject, abstain
|
|
166
|
+
# Adversarial debate fields
|
|
167
|
+
persona_name: Optional[str] = None
|
|
168
|
+
challenges_raised: list[str] = field(default_factory=list)
|
|
169
|
+
concessions_made: list[str] = field(default_factory=list)
|
|
170
|
+
key_arguments: list[str] = field(default_factory=list)
|
|
92
171
|
|
|
93
172
|
def to_dict(self) -> dict:
|
|
94
173
|
return {
|
|
@@ -103,6 +182,9 @@ class AgentResponse:
|
|
|
103
182
|
"approvals": self.approvals,
|
|
104
183
|
"suggestions": self.suggestions,
|
|
105
184
|
"vote": self.vote,
|
|
185
|
+
"persona_name": self.persona_name,
|
|
186
|
+
"challenges_raised": self.challenges_raised,
|
|
187
|
+
"key_arguments": self.key_arguments[:5],
|
|
106
188
|
}
|
|
107
189
|
|
|
108
190
|
|
|
@@ -128,7 +210,7 @@ class SwarmIteration:
|
|
|
128
210
|
|
|
129
211
|
@dataclass
|
|
130
212
|
class SwarmResult:
|
|
131
|
-
"""Result of swarm orchestration."""
|
|
213
|
+
"""Result of adversarial swarm orchestration."""
|
|
132
214
|
|
|
133
215
|
story_key: str
|
|
134
216
|
task: str
|
|
@@ -141,6 +223,9 @@ class SwarmResult:
|
|
|
141
223
|
start_time: str
|
|
142
224
|
end_time: str
|
|
143
225
|
consensus_type: ConsensusType
|
|
226
|
+
# Adversarial debate results
|
|
227
|
+
personality_handoff: Optional[PersonalityHandoff] = None
|
|
228
|
+
termination_reason: str = "max_iterations" # consensus, convergence, budget, max_iterations
|
|
144
229
|
|
|
145
230
|
def to_dict(self) -> dict:
|
|
146
231
|
return {
|
|
@@ -155,25 +240,63 @@ class SwarmResult:
|
|
|
155
240
|
"start_time": self.start_time,
|
|
156
241
|
"end_time": self.end_time,
|
|
157
242
|
"consensus_type": self.consensus_type.value,
|
|
243
|
+
"termination_reason": self.termination_reason,
|
|
244
|
+
"personality_handoff": (
|
|
245
|
+
self.personality_handoff.to_dict() if self.personality_handoff else None
|
|
246
|
+
),
|
|
158
247
|
}
|
|
159
248
|
|
|
160
249
|
def to_summary(self) -> str:
|
|
161
|
-
"""Generate a human-readable summary."""
|
|
250
|
+
"""Generate a human-readable summary of the adversarial debate."""
|
|
162
251
|
lines = [
|
|
163
|
-
f"## Swarm Result: {self.story_key}",
|
|
252
|
+
f"## Adversarial Swarm Result: {self.story_key}",
|
|
164
253
|
"",
|
|
165
254
|
f"**Task**: {self.task}",
|
|
166
255
|
f"**State**: {self.state.value}",
|
|
167
|
-
f"**
|
|
168
|
-
f"**
|
|
256
|
+
f"**Debate Rounds**: {len(self.iterations)}",
|
|
257
|
+
f"**Termination**: {self.termination_reason}",
|
|
169
258
|
f"**Total Cost**: ${self.total_cost_usd:.4f}",
|
|
170
259
|
"",
|
|
171
260
|
]
|
|
172
261
|
|
|
262
|
+
# Show participating personas
|
|
263
|
+
if self.personality_handoff:
|
|
264
|
+
lines.append("### Adversarial Personas")
|
|
265
|
+
for p in self.personality_handoff.selected_personas:
|
|
266
|
+
stance = (
|
|
267
|
+
f" [Focus: {p.adversarial_stance.primary_concern}]"
|
|
268
|
+
if p.adversarial_stance
|
|
269
|
+
else ""
|
|
270
|
+
)
|
|
271
|
+
lines.append(f"- **{p.name}** ({p.agent_type}){stance}")
|
|
272
|
+
lines.append("")
|
|
273
|
+
|
|
173
274
|
if self.state == SwarmState.CONSENSUS:
|
|
174
|
-
lines.append("
|
|
275
|
+
lines.append("[CONSENSUS] Agents reached agreement!")
|
|
276
|
+
elif self.termination_reason == "convergence":
|
|
277
|
+
lines.append("[CONVERGED] Positions stabilized - forcing consensus")
|
|
278
|
+
elif self.termination_reason == "budget":
|
|
279
|
+
lines.append("[BUDGET] Token budget reached - forcing consensus")
|
|
175
280
|
elif self.state == SwarmState.MAX_ITERATIONS:
|
|
176
|
-
lines.append("
|
|
281
|
+
lines.append("[MAX ROUNDS] Iteration limit reached")
|
|
282
|
+
|
|
283
|
+
# Show consensus points and tensions
|
|
284
|
+
if self.personality_handoff:
|
|
285
|
+
if self.personality_handoff.consensus_points:
|
|
286
|
+
lines.append("")
|
|
287
|
+
lines.append("### Points of Agreement")
|
|
288
|
+
for point in self.personality_handoff.consensus_points:
|
|
289
|
+
lines.append(f"- {point}")
|
|
290
|
+
|
|
291
|
+
if self.personality_handoff.unresolved_tensions:
|
|
292
|
+
lines.append("")
|
|
293
|
+
lines.append("### Unresolved Tensions")
|
|
294
|
+
for tension in self.personality_handoff.unresolved_tensions:
|
|
295
|
+
lines.append(f"- [WARNING] {tension}")
|
|
296
|
+
|
|
297
|
+
lines.append("")
|
|
298
|
+
lines.append("### Recommended Approach")
|
|
299
|
+
lines.append(self.personality_handoff.recommended_approach)
|
|
177
300
|
|
|
178
301
|
lines.append("")
|
|
179
302
|
lines.append("### Final Output")
|
|
@@ -186,16 +309,22 @@ class SwarmResult:
|
|
|
186
309
|
|
|
187
310
|
@dataclass
|
|
188
311
|
class SwarmConfig:
|
|
189
|
-
"""Configuration for swarm orchestration."""
|
|
312
|
+
"""Configuration for adversarial swarm orchestration."""
|
|
190
313
|
|
|
191
|
-
max_iterations: int = 3
|
|
192
|
-
consensus_type: ConsensusType = ConsensusType.
|
|
314
|
+
max_iterations: int = 3 # Limited to 3 rounds - diminishing returns after this
|
|
315
|
+
consensus_type: ConsensusType = ConsensusType.MAJORITY
|
|
193
316
|
quorum_size: int = 2 # For QUORUM type
|
|
194
317
|
timeout_seconds: int = 300
|
|
195
318
|
parallel_execution: bool = False
|
|
196
319
|
auto_fix_enabled: bool = True # DEV automatically addresses REVIEWER issues
|
|
197
320
|
verbose: bool = True
|
|
198
|
-
budget_limit_usd: float =
|
|
321
|
+
budget_limit_usd: float = 25.0 # Higher budget for debate
|
|
322
|
+
validation_enabled: bool = True # Enable inter-iteration validation
|
|
323
|
+
# Adversarial-specific settings
|
|
324
|
+
convergence_threshold: float = 0.8 # Position similarity to consider converged
|
|
325
|
+
stability_rounds: int = 2 # Rounds of stability before convergence
|
|
326
|
+
force_consensus_on_budget: bool = True # Force agreement when budget exhausted
|
|
327
|
+
cross_challenge_enabled: bool = True # Agents directly challenge each other
|
|
199
328
|
|
|
200
329
|
def to_dict(self) -> dict:
|
|
201
330
|
return {
|
|
@@ -206,11 +335,20 @@ class SwarmConfig:
|
|
|
206
335
|
"parallel_execution": self.parallel_execution,
|
|
207
336
|
"auto_fix_enabled": self.auto_fix_enabled,
|
|
208
337
|
"budget_limit_usd": self.budget_limit_usd,
|
|
338
|
+
"validation_enabled": self.validation_enabled,
|
|
339
|
+
"convergence_threshold": self.convergence_threshold,
|
|
340
|
+
"stability_rounds": self.stability_rounds,
|
|
341
|
+
"cross_challenge_enabled": self.cross_challenge_enabled,
|
|
209
342
|
}
|
|
210
343
|
|
|
211
344
|
|
|
212
345
|
class SwarmOrchestrator:
|
|
213
|
-
"""Orchestrates multi-agent collaboration.
|
|
346
|
+
"""Orchestrates adversarial multi-agent collaboration.
|
|
347
|
+
|
|
348
|
+
Agents are assigned opposing personas and debate until consensus
|
|
349
|
+
or convergence is reached. This creates robust solutions through
|
|
350
|
+
competitive pressure and cross-examination.
|
|
351
|
+
"""
|
|
214
352
|
|
|
215
353
|
def __init__(self, story_key: str, config: Optional[SwarmConfig] = None):
|
|
216
354
|
self.story_key = story_key
|
|
@@ -225,8 +363,19 @@ class SwarmOrchestrator:
|
|
|
225
363
|
self.iterations: list[SwarmIteration] = []
|
|
226
364
|
self.total_tokens = 0
|
|
227
365
|
self.total_cost = 0.0
|
|
366
|
+
self.termination_reason = "max_iterations"
|
|
367
|
+
|
|
368
|
+
# Adversarial personality system
|
|
369
|
+
self.personality_selector = PersonalitySelector()
|
|
370
|
+
self.convergence_detector = ConvergenceDetector(
|
|
371
|
+
similarity_threshold=self.config.convergence_threshold,
|
|
372
|
+
stability_rounds=self.config.stability_rounds,
|
|
373
|
+
)
|
|
374
|
+
self.selected_personas: list[PersonalityProfile] = []
|
|
375
|
+
self.agent_personas: dict[str, PersonalityProfile] = {}
|
|
376
|
+
self.debate_positions: dict[str, DebatePosition] = {}
|
|
228
377
|
|
|
229
|
-
# Agent model mapping
|
|
378
|
+
# Agent model mapping (can be overridden by persona)
|
|
230
379
|
self.agent_models = {
|
|
231
380
|
"SM": "sonnet",
|
|
232
381
|
"DEV": "opus",
|
|
@@ -239,6 +388,20 @@ class SwarmOrchestrator:
|
|
|
239
388
|
"SECURITY": "opus",
|
|
240
389
|
}
|
|
241
390
|
|
|
391
|
+
# Initialize validation loop if available and enabled
|
|
392
|
+
self.validation_loop = None
|
|
393
|
+
self.validation_context = None
|
|
394
|
+
if HAS_VALIDATION and self.config.validation_enabled:
|
|
395
|
+
self.validation_loop = ValidationLoop(
|
|
396
|
+
gates=INTER_PHASE_GATES,
|
|
397
|
+
config={"auto_fix_enabled": self.config.auto_fix_enabled},
|
|
398
|
+
story_key=story_key,
|
|
399
|
+
)
|
|
400
|
+
self.validation_context = LoopContext(
|
|
401
|
+
story_key=story_key,
|
|
402
|
+
max_iterations=self.config.max_iterations,
|
|
403
|
+
)
|
|
404
|
+
|
|
242
405
|
def _log(self, message: str, level: str = "INFO"):
|
|
243
406
|
"""Log a message with timestamp."""
|
|
244
407
|
if self.config.verbose:
|
|
@@ -252,15 +415,54 @@ class SwarmOrchestrator:
|
|
|
252
415
|
}.get(level, "•")
|
|
253
416
|
print(f"[{timestamp}] {emoji} {message}")
|
|
254
417
|
|
|
418
|
+
def _select_adversarial_personas(self, agents: list[str], task: str):
|
|
419
|
+
"""Select adversarial personas for the given agents and task."""
|
|
420
|
+
self._log("Selecting adversarial personas for debate...")
|
|
421
|
+
|
|
422
|
+
# Get personas from personality selector
|
|
423
|
+
self.selected_personas = self.personality_selector.select_adversarial_personas(
|
|
424
|
+
task=task,
|
|
425
|
+
num_agents=len(agents),
|
|
426
|
+
required_agents=agents,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# Map personas to agents
|
|
430
|
+
for i, agent in enumerate(agents):
|
|
431
|
+
if i < len(self.selected_personas):
|
|
432
|
+
persona = self.selected_personas[i]
|
|
433
|
+
self.agent_personas[agent] = persona
|
|
434
|
+
# Override model if persona specifies
|
|
435
|
+
if persona.model:
|
|
436
|
+
self.agent_models[agent] = persona.model
|
|
437
|
+
self._log(
|
|
438
|
+
f" {agent} -> {persona.name} "
|
|
439
|
+
f"[{persona.adversarial_stance.primary_concern if persona.adversarial_stance else 'general'}]"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Initialize debate position tracking
|
|
443
|
+
self.debate_positions[agent] = DebatePosition(
|
|
444
|
+
agent=agent,
|
|
445
|
+
persona_name=persona.name,
|
|
446
|
+
)
|
|
447
|
+
|
|
255
448
|
def _invoke_agent(self, agent: str, prompt: str, iteration: int = 0) -> AgentResponse:
|
|
256
|
-
"""Invoke a single agent with Claude CLI."""
|
|
449
|
+
"""Invoke a single agent with Claude CLI, including adversarial persona."""
|
|
257
450
|
model = self.agent_models.get(agent, "sonnet")
|
|
451
|
+
persona = self.agent_personas.get(agent)
|
|
452
|
+
persona_name = persona.name if persona else None
|
|
258
453
|
|
|
259
|
-
|
|
454
|
+
persona_label = f" as '{persona_name}'" if persona_name else ""
|
|
455
|
+
self._log(f"Invoking {agent}{persona_label} (model: {model})...")
|
|
260
456
|
|
|
261
|
-
# Build the full prompt with context
|
|
457
|
+
# Build the full prompt with context and persona
|
|
262
458
|
context = self.handoff_generator.generate_context_for_agent(agent)
|
|
263
|
-
|
|
459
|
+
|
|
460
|
+
# Inject persona if available
|
|
461
|
+
persona_injection = ""
|
|
462
|
+
if persona:
|
|
463
|
+
persona_injection = persona.to_prompt_injection() + "\n\n---\n\n"
|
|
464
|
+
|
|
465
|
+
full_prompt = _sanitize_prompt(f"{persona_injection}{context}\n\n---\n\n{prompt}")
|
|
264
466
|
|
|
265
467
|
try:
|
|
266
468
|
result = subprocess.run(
|
|
@@ -279,6 +481,18 @@ class SwarmOrchestrator:
|
|
|
279
481
|
suggestions = self._extract_suggestions(content)
|
|
280
482
|
vote = self._determine_vote(content, issues, approvals)
|
|
281
483
|
|
|
484
|
+
# Extract adversarial elements
|
|
485
|
+
key_arguments = extract_arguments_from_response(content)
|
|
486
|
+
challenges = self._extract_challenges(content)
|
|
487
|
+
concessions = self._extract_concessions(content)
|
|
488
|
+
|
|
489
|
+
# Update debate position
|
|
490
|
+
if agent in self.debate_positions:
|
|
491
|
+
pos = self.debate_positions[agent]
|
|
492
|
+
pos.key_arguments = key_arguments
|
|
493
|
+
pos.challenges_raised = challenges
|
|
494
|
+
pos.concessions_made.extend(concessions)
|
|
495
|
+
|
|
282
496
|
# Estimate tokens (rough)
|
|
283
497
|
tokens = len(full_prompt.split()) + len(content.split())
|
|
284
498
|
cost = self._estimate_cost(tokens, model)
|
|
@@ -298,6 +512,10 @@ class SwarmOrchestrator:
|
|
|
298
512
|
approvals=approvals,
|
|
299
513
|
suggestions=suggestions,
|
|
300
514
|
vote=vote,
|
|
515
|
+
persona_name=persona_name,
|
|
516
|
+
challenges_raised=challenges,
|
|
517
|
+
concessions_made=concessions,
|
|
518
|
+
key_arguments=key_arguments,
|
|
301
519
|
)
|
|
302
520
|
|
|
303
521
|
except subprocess.TimeoutExpired:
|
|
@@ -309,6 +527,7 @@ class SwarmOrchestrator:
|
|
|
309
527
|
timestamp=datetime.now().isoformat(),
|
|
310
528
|
iteration=iteration,
|
|
311
529
|
vote="abstain",
|
|
530
|
+
persona_name=persona_name,
|
|
312
531
|
)
|
|
313
532
|
except Exception as e:
|
|
314
533
|
self._log(f"{agent} failed: {e}", "ERROR")
|
|
@@ -319,8 +538,39 @@ class SwarmOrchestrator:
|
|
|
319
538
|
timestamp=datetime.now().isoformat(),
|
|
320
539
|
iteration=iteration,
|
|
321
540
|
vote="abstain",
|
|
541
|
+
persona_name=persona_name,
|
|
322
542
|
)
|
|
323
543
|
|
|
544
|
+
def _extract_challenges(self, content: str) -> list[str]:
|
|
545
|
+
"""Extract challenges/objections from adversarial response."""
|
|
546
|
+
challenges = []
|
|
547
|
+
patterns = [
|
|
548
|
+
r"(?:I challenge|I disagree|I object|However|But|My concern is)[\s:]+(.+?)(?:\.|$)",
|
|
549
|
+
r"(?:This ignores|This overlooks|What about)[\s:]+(.+?)(?:\.|$)",
|
|
550
|
+
r"\[CHALLENGE\]\s*(.+)",
|
|
551
|
+
]
|
|
552
|
+
|
|
553
|
+
for pattern in patterns:
|
|
554
|
+
matches = re.findall(pattern, content, re.IGNORECASE | re.MULTILINE)
|
|
555
|
+
challenges.extend(matches)
|
|
556
|
+
|
|
557
|
+
return list({c.strip() for c in challenges if len(c) > 10})[:5]
|
|
558
|
+
|
|
559
|
+
def _extract_concessions(self, content: str) -> list[str]:
|
|
560
|
+
"""Extract concessions from adversarial response."""
|
|
561
|
+
concessions = []
|
|
562
|
+
patterns = [
|
|
563
|
+
r"(?:I concede|I agree that|Fair point|You're right that|I accept)[\s:]+(.+?)(?:\.|$)",
|
|
564
|
+
r"(?:On reflection|Reconsidering)[\s:]+(.+?)(?:\.|$)",
|
|
565
|
+
r"\[CONCEDE\]\s*(.+)",
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
for pattern in patterns:
|
|
569
|
+
matches = re.findall(pattern, content, re.IGNORECASE | re.MULTILINE)
|
|
570
|
+
concessions.extend(matches)
|
|
571
|
+
|
|
572
|
+
return list({c.strip() for c in concessions if len(c) > 10})[:5]
|
|
573
|
+
|
|
324
574
|
def _extract_issues(self, content: str) -> list[str]:
|
|
325
575
|
"""Extract issues/problems from response."""
|
|
326
576
|
issues = []
|
|
@@ -440,6 +690,40 @@ class SwarmOrchestrator:
|
|
|
440
690
|
all_issues.extend(r.issues_found)
|
|
441
691
|
return list(set(all_issues))
|
|
442
692
|
|
|
693
|
+
def _run_iteration_validation(self, iteration: int, responses: list[AgentResponse]) -> bool:
|
|
694
|
+
"""Run validation between iterations.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
iteration: Current iteration number
|
|
698
|
+
responses: Agent responses from this iteration
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
True if validation passed, False otherwise
|
|
702
|
+
"""
|
|
703
|
+
if not self.validation_loop or not self.validation_context:
|
|
704
|
+
return True
|
|
705
|
+
|
|
706
|
+
self.validation_context.iteration = iteration
|
|
707
|
+
self.validation_context.phase = f"iteration_{iteration}"
|
|
708
|
+
|
|
709
|
+
# Update context with iteration data
|
|
710
|
+
self.validation_context.accumulated_issues = self._collect_issues(responses)
|
|
711
|
+
|
|
712
|
+
report = self.validation_loop.run_gates(self.validation_context, tier=2)
|
|
713
|
+
|
|
714
|
+
if report.passed:
|
|
715
|
+
self._log(f"[VALIDATION] Iteration {iteration + 1} passed validation")
|
|
716
|
+
return True
|
|
717
|
+
else:
|
|
718
|
+
# Log warnings but don't block swarm - just record issues
|
|
719
|
+
for failure in report.failures:
|
|
720
|
+
self._log(f"[VALIDATION] {failure.gate_name}: {failure.message}", "WARN")
|
|
721
|
+
# Add to accumulated issues for next iteration
|
|
722
|
+
self.validation_context.accumulated_issues.append(
|
|
723
|
+
f"Validation: {failure.gate_name} - {failure.message}"
|
|
724
|
+
)
|
|
725
|
+
return True # Don't block swarm, just inform
|
|
726
|
+
|
|
443
727
|
def _build_iteration_prompt(
|
|
444
728
|
self,
|
|
445
729
|
agent: str,
|
|
@@ -448,52 +732,110 @@ class SwarmOrchestrator:
|
|
|
448
732
|
previous_responses: list[AgentResponse],
|
|
449
733
|
issues_to_fix: list[str],
|
|
450
734
|
) -> str:
|
|
451
|
-
"""Build prompt for a specific iteration."""
|
|
735
|
+
"""Build adversarial debate prompt for a specific iteration."""
|
|
736
|
+
persona = self.agent_personas.get(agent)
|
|
737
|
+
stance_info = ""
|
|
738
|
+
if persona and persona.adversarial_stance:
|
|
739
|
+
stance = persona.adversarial_stance
|
|
740
|
+
stance_info = f"""
|
|
741
|
+
Your stance in this debate:
|
|
742
|
+
- Primary concern: {stance.primary_concern}
|
|
743
|
+
- Challenge arguments that prioritize: {', '.join(stance.opposes) if stance.opposes else 'shortcuts or poor quality'}
|
|
744
|
+
- Debate style: {stance.debate_style}
|
|
745
|
+
"""
|
|
452
746
|
|
|
453
747
|
if iteration == 0:
|
|
454
|
-
# First iteration -
|
|
455
|
-
return f"""
|
|
748
|
+
# First iteration - state position and challenge others
|
|
749
|
+
return f"""## ADVERSARIAL DEBATE - Round 1
|
|
750
|
+
|
|
751
|
+
You are the {agent} agent participating in an adversarial design debate.
|
|
752
|
+
Your goal is to advocate for your perspective while challenging others.
|
|
456
753
|
|
|
754
|
+
### The Task
|
|
457
755
|
{task}
|
|
458
756
|
|
|
459
|
-
|
|
460
|
-
|
|
757
|
+
{stance_info}
|
|
758
|
+
|
|
759
|
+
### Instructions
|
|
760
|
+
1. State your position on how to approach this task
|
|
761
|
+
2. Identify potential issues, risks, or overlooked concerns
|
|
762
|
+
3. Be specific about what approach you advocate for and WHY
|
|
763
|
+
4. Anticipate counter-arguments and address them preemptively
|
|
764
|
+
|
|
765
|
+
### Response Format
|
|
766
|
+
Structure your response with:
|
|
767
|
+
- **My Position**: Your recommended approach
|
|
768
|
+
- **Key Arguments**: Why this is the right approach (be specific)
|
|
769
|
+
- **Concerns**: What could go wrong with other approaches
|
|
770
|
+
- **Challenges for Others**: Questions or objections for other agents to address
|
|
771
|
+
|
|
772
|
+
At the end, indicate: APPROVE (if you'd accept current direction) or ISSUES (if concerns remain)
|
|
461
773
|
"""
|
|
462
774
|
|
|
463
|
-
# Subsequent iterations -
|
|
464
|
-
|
|
775
|
+
# Subsequent iterations - adversarial cross-challenge
|
|
776
|
+
other_positions = []
|
|
465
777
|
for r in previous_responses:
|
|
466
778
|
if r.agent == agent:
|
|
467
779
|
continue
|
|
468
780
|
|
|
469
|
-
|
|
781
|
+
persona_label = f" ({r.persona_name})" if r.persona_name else ""
|
|
782
|
+
|
|
783
|
+
position_block = [f"### {r.agent}{persona_label}"]
|
|
784
|
+
|
|
785
|
+
# Show their key arguments
|
|
786
|
+
if r.key_arguments:
|
|
787
|
+
position_block.append("**Their Arguments:**")
|
|
788
|
+
for arg in r.key_arguments[:3]:
|
|
789
|
+
position_block.append(f"- {arg}")
|
|
790
|
+
|
|
791
|
+
# Show challenges they raised
|
|
792
|
+
if r.challenges_raised:
|
|
793
|
+
position_block.append("**Challenges They Raised:**")
|
|
794
|
+
for challenge in r.challenges_raised[:3]:
|
|
795
|
+
position_block.append(f"- {challenge}")
|
|
796
|
+
|
|
797
|
+
# Show their issues
|
|
470
798
|
if r.issues_found:
|
|
471
|
-
|
|
472
|
-
for issue in r.issues_found:
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
799
|
+
position_block.append("**Issues They Identified:**")
|
|
800
|
+
for issue in r.issues_found[:3]:
|
|
801
|
+
position_block.append(f"- {issue}")
|
|
802
|
+
|
|
803
|
+
# Show their vote
|
|
804
|
+
if r.vote:
|
|
805
|
+
position_block.append(f"**Their Vote**: {r.vote.upper()}")
|
|
806
|
+
|
|
807
|
+
position_block.append("")
|
|
808
|
+
other_positions.append("\n".join(position_block))
|
|
809
|
+
|
|
810
|
+
prompt = f"""## ADVERSARIAL DEBATE - Round {iteration + 1}
|
|
811
|
+
|
|
812
|
+
You are the {agent} agent. This is round {iteration + 1} of the debate.
|
|
813
|
+
|
|
814
|
+
### Original Task
|
|
487
815
|
{task}
|
|
488
816
|
|
|
489
|
-
|
|
490
|
-
|
|
817
|
+
{stance_info}
|
|
818
|
+
|
|
819
|
+
### Other Agents' Positions
|
|
820
|
+
{chr(10).join(other_positions) if other_positions else "No other positions yet."}
|
|
491
821
|
|
|
492
|
-
|
|
493
|
-
{chr(10).join(f"- {issue}" for issue in issues_to_fix) if issues_to_fix else "
|
|
822
|
+
### Outstanding Issues
|
|
823
|
+
{chr(10).join(f"- {issue}" for issue in issues_to_fix) if issues_to_fix else "None identified."}
|
|
494
824
|
|
|
495
|
-
|
|
496
|
-
|
|
825
|
+
### Your Task This Round
|
|
826
|
+
1. **Respond to challenges**: Address objections raised against your position
|
|
827
|
+
2. **Challenge others**: Push back on weak arguments or overlooked concerns
|
|
828
|
+
3. **Find common ground**: Identify points of agreement where possible
|
|
829
|
+
4. **Refine your position**: Adjust based on valid critiques (concede if warranted)
|
|
830
|
+
|
|
831
|
+
### Response Format
|
|
832
|
+
- **Response to Challenges**: Address specific objections to your position
|
|
833
|
+
- **Counter-Arguments**: Challenge weak points in others' positions
|
|
834
|
+
- **Concessions**: Points you're willing to concede (use [CONCEDE] prefix)
|
|
835
|
+
- **Updated Position**: Your refined stance after this round
|
|
836
|
+
- **Remaining Concerns**: Issues that still need resolution
|
|
837
|
+
|
|
838
|
+
End with: APPROVE (ready to move forward) or ISSUES (significant concerns remain)
|
|
497
839
|
"""
|
|
498
840
|
|
|
499
841
|
return prompt
|
|
@@ -501,25 +843,40 @@ At the end, clearly indicate if you APPROVE the current state or have remaining
|
|
|
501
843
|
def run_swarm(
|
|
502
844
|
self, agents: list[str], task: str, max_iterations: Optional[int] = None
|
|
503
845
|
) -> SwarmResult:
|
|
504
|
-
"""Run swarm orchestration with multiple agents.
|
|
846
|
+
"""Run adversarial swarm orchestration with multiple agents.
|
|
847
|
+
|
|
848
|
+
Agents are assigned opposing personas and debate until consensus,
|
|
849
|
+
convergence, or budget exhaustion.
|
|
850
|
+
"""
|
|
505
851
|
|
|
506
852
|
max_iter = max_iterations or self.config.max_iterations
|
|
507
853
|
start_time = datetime.now().isoformat()
|
|
508
854
|
|
|
509
855
|
self.state = SwarmState.RUNNING
|
|
510
|
-
self._log(f"Starting swarm with agents: {', '.join(agents)}")
|
|
856
|
+
self._log(f"Starting ADVERSARIAL swarm with agents: {', '.join(agents)}")
|
|
511
857
|
self._log(f"Task: {task[:100]}...")
|
|
512
858
|
|
|
859
|
+
# Select adversarial personas for each agent
|
|
860
|
+
self._select_adversarial_personas(agents, task)
|
|
861
|
+
|
|
513
862
|
issues_to_fix: list[str] = []
|
|
514
863
|
previous_responses: list[AgentResponse] = []
|
|
515
864
|
|
|
516
865
|
for iteration in range(max_iter):
|
|
517
|
-
self._log(f"===
|
|
866
|
+
self._log(f"=== Debate Round {iteration + 1}/{max_iter} ===")
|
|
518
867
|
self.state = SwarmState.DEBATING
|
|
519
868
|
|
|
520
|
-
# Check budget
|
|
869
|
+
# Check budget limit
|
|
521
870
|
if self.total_cost >= self.config.budget_limit_usd:
|
|
522
|
-
self._log("Budget limit reached
|
|
871
|
+
self._log("[BUDGET] Budget limit reached - forcing consensus", "WARNING")
|
|
872
|
+
self.termination_reason = "budget"
|
|
873
|
+
break
|
|
874
|
+
|
|
875
|
+
# Check convergence (hybrid: positions stabilized)
|
|
876
|
+
if iteration > 0 and self.convergence_detector.has_converged():
|
|
877
|
+
self._log("[CONVERGED] Positions have stabilized - ending debate", "SUCCESS")
|
|
878
|
+
self.termination_reason = "convergence"
|
|
879
|
+
self.state = SwarmState.CONVERGING
|
|
523
880
|
break
|
|
524
881
|
|
|
525
882
|
iter_responses: list[AgentResponse] = []
|
|
@@ -558,6 +915,16 @@ At the end, clearly indicate if you APPROVE the current state or have remaining
|
|
|
558
915
|
# Collect issues
|
|
559
916
|
issues_to_fix = self._collect_issues(iter_responses)
|
|
560
917
|
|
|
918
|
+
# Record positions for convergence detection
|
|
919
|
+
round_positions = {}
|
|
920
|
+
for r in iter_responses:
|
|
921
|
+
if r.agent in self.debate_positions:
|
|
922
|
+
round_positions[r.agent] = self.debate_positions[r.agent]
|
|
923
|
+
self.convergence_detector.record_round(round_positions)
|
|
924
|
+
|
|
925
|
+
# Run validation between iterations
|
|
926
|
+
self._run_iteration_validation(iteration, iter_responses)
|
|
927
|
+
|
|
561
928
|
# Check consensus
|
|
562
929
|
consensus = self._check_consensus(iter_responses)
|
|
563
930
|
|
|
@@ -572,20 +939,28 @@ At the end, clearly indicate if you APPROVE the current state or have remaining
|
|
|
572
939
|
|
|
573
940
|
previous_responses = iter_responses
|
|
574
941
|
|
|
942
|
+
# Log debate status
|
|
943
|
+
agreement_score = self.convergence_detector.calculate_agreement_score()
|
|
575
944
|
self._log(f"Issues remaining: {len(issues_to_fix)}")
|
|
576
|
-
self._log(f"
|
|
945
|
+
self._log(f"Agreement score: {agreement_score:.0%}")
|
|
946
|
+
self._log(f"Consensus: {'[YES]' if consensus else '[NO]'}")
|
|
577
947
|
|
|
578
948
|
if consensus:
|
|
579
949
|
self.state = SwarmState.CONSENSUS
|
|
580
|
-
self.
|
|
950
|
+
self.termination_reason = "consensus"
|
|
951
|
+
self._log("[CONSENSUS] Agents reached agreement!", "SUCCESS")
|
|
581
952
|
break
|
|
582
953
|
|
|
583
954
|
# Determine final state
|
|
584
|
-
if self.state
|
|
955
|
+
if self.state not in (SwarmState.CONSENSUS, SwarmState.CONVERGING):
|
|
585
956
|
self.state = SwarmState.MAX_ITERATIONS
|
|
957
|
+
self.termination_reason = "max_iterations"
|
|
586
958
|
|
|
587
|
-
# Generate final output
|
|
959
|
+
# Generate final output and personality handoff
|
|
588
960
|
final_output = self._generate_final_output(previous_responses)
|
|
961
|
+
personality_handoff = self._generate_personality_handoff(
|
|
962
|
+
task, previous_responses, final_output
|
|
963
|
+
)
|
|
589
964
|
|
|
590
965
|
# Create result
|
|
591
966
|
result = SwarmResult(
|
|
@@ -600,18 +975,82 @@ At the end, clearly indicate if you APPROVE the current state or have remaining
|
|
|
600
975
|
start_time=start_time,
|
|
601
976
|
end_time=datetime.now().isoformat(),
|
|
602
977
|
consensus_type=self.config.consensus_type,
|
|
978
|
+
personality_handoff=personality_handoff,
|
|
979
|
+
termination_reason=self.termination_reason,
|
|
603
980
|
)
|
|
604
981
|
|
|
605
982
|
# Save to knowledge graph
|
|
606
983
|
self.knowledge_graph.add_decision(
|
|
607
984
|
agent="SWARM",
|
|
608
|
-
topic="
|
|
609
|
-
decision=f"Completed
|
|
610
|
-
context={
|
|
985
|
+
topic="adversarial-debate-result",
|
|
986
|
+
decision=f"Completed: {self.termination_reason} after {len(self.iterations)} rounds",
|
|
987
|
+
context={
|
|
988
|
+
"iterations": len(self.iterations),
|
|
989
|
+
"cost": self.total_cost,
|
|
990
|
+
"termination": self.termination_reason,
|
|
991
|
+
"personas": [p.name for p in self.selected_personas],
|
|
992
|
+
},
|
|
611
993
|
)
|
|
612
994
|
|
|
613
995
|
return result
|
|
614
996
|
|
|
997
|
+
def _generate_personality_handoff(
|
|
998
|
+
self,
|
|
999
|
+
task: str,
|
|
1000
|
+
responses: list[AgentResponse],
|
|
1001
|
+
final_output: str,
|
|
1002
|
+
) -> PersonalityHandoff:
|
|
1003
|
+
"""Generate a personality handoff summarizing the adversarial debate."""
|
|
1004
|
+
|
|
1005
|
+
# Extract consensus points and tensions
|
|
1006
|
+
response_dicts = [{"content": r.content} for r in responses]
|
|
1007
|
+
consensus_points, tensions = self.convergence_detector.extract_consensus_points(
|
|
1008
|
+
response_dicts
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
# Generate debate summary
|
|
1012
|
+
debate_summary = self.convergence_detector.summarize_debate(
|
|
1013
|
+
[i.to_dict() for i in self.iterations]
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
# Synthesize recommended approach from final responses
|
|
1017
|
+
recommended = self._synthesize_recommendation(responses)
|
|
1018
|
+
|
|
1019
|
+
return PersonalityHandoff(
|
|
1020
|
+
spawned_by="SWARM_ORCHESTRATOR",
|
|
1021
|
+
selected_personas=self.selected_personas,
|
|
1022
|
+
debate_summary=debate_summary,
|
|
1023
|
+
consensus_points=consensus_points,
|
|
1024
|
+
unresolved_tensions=tensions,
|
|
1025
|
+
recommended_approach=recommended,
|
|
1026
|
+
confidence=self.convergence_detector.calculate_agreement_score(),
|
|
1027
|
+
total_rounds=len(self.iterations),
|
|
1028
|
+
termination_reason=self.termination_reason,
|
|
1029
|
+
positions=list(self.debate_positions.values()),
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
def _synthesize_recommendation(self, responses: list[AgentResponse]) -> str:
|
|
1033
|
+
"""Synthesize a recommended approach from debate responses."""
|
|
1034
|
+
# Collect all key arguments from approving agents
|
|
1035
|
+
approving_args = []
|
|
1036
|
+
for r in responses:
|
|
1037
|
+
if r.vote == "approve" and r.key_arguments:
|
|
1038
|
+
approving_args.extend(r.key_arguments[:2])
|
|
1039
|
+
|
|
1040
|
+
if approving_args:
|
|
1041
|
+
return "Based on debate consensus: " + "; ".join(approving_args[:3])
|
|
1042
|
+
|
|
1043
|
+
# Fallback to general synthesis
|
|
1044
|
+
all_args = []
|
|
1045
|
+
for r in responses:
|
|
1046
|
+
if r.key_arguments:
|
|
1047
|
+
all_args.extend(r.key_arguments[:1])
|
|
1048
|
+
|
|
1049
|
+
if all_args:
|
|
1050
|
+
return "Synthesized from debate: " + "; ".join(all_args[:3])
|
|
1051
|
+
|
|
1052
|
+
return "No clear recommendation emerged - human review recommended."
|
|
1053
|
+
|
|
615
1054
|
def _generate_final_output(self, responses: list[AgentResponse]) -> str:
|
|
616
1055
|
"""Generate consolidated final output."""
|
|
617
1056
|
# Use the DEV response as primary, or last response
|