The standalone prototype should be the root-level project shape for fengqun while preserving the existing planning documents already at the root. This keeps README, examples, tests, and the Python package directly discoverable without deleting the prior docs. Constraint: User clarified that swarm-minimal is the repository root, but other existing root files must remain. Rejected: Deleting existing root docs | They are part of the fengqun repository context and were explicitly protected. Confidence: high Scope-risk: narrow Directive: Keep secrets in ignored .env only; do not commit live credentials. Tested: python3 -B -m unittest discover -s tests; git diff --check; secret-pattern scan showed only placeholders/test values/task-id false positives. Not-tested: Remote web UI rendering after push.
171 lines
8.2 KiB
Python
171 lines
8.2 KiB
Python
import unittest
|
|
|
|
from swarm_minimal.core import Agent, InMemorySwarmStore, SwarmCoordinator, Task, TaskStatus
|
|
|
|
|
|
class SwarmBehaviorAcademicTest(unittest.TestCase):
|
|
def test_single_agnet_failure_isolated_by_redundant_convergence(self) -> None:
|
|
store = InMemorySwarmStore()
|
|
run_id = "behavior-fault-isolation"
|
|
store.shared_state[f"run:{run_id}:goal"] = "fault isolation with redundant routes"
|
|
store.shared_state[f"run:{run_id}:status"] = "running"
|
|
store.add_task(Task(kind="route", input="fragile-route"))
|
|
store.add_task(Task(kind="route", input="robust-route-a"))
|
|
store.add_task(Task(kind="route", input="robust-route-b"))
|
|
|
|
agents = [
|
|
Agent(
|
|
id="crashing-agnet",
|
|
capability="route",
|
|
run=lambda task, _: (_ for _ in ()).throw(RuntimeError("single agnet crashed")),
|
|
),
|
|
Agent(
|
|
id="backup-agnet-a",
|
|
capability="route",
|
|
run=lambda task, _: (f"healthy result from {task.input}", 0.91),
|
|
),
|
|
Agent(
|
|
id="backup-agnet-b",
|
|
capability="route",
|
|
run=lambda task, _: (f"alternative result from {task.input}", 0.86),
|
|
),
|
|
]
|
|
|
|
result = SwarmCoordinator(store=store, agents=agents).run_until_converged(run_id)
|
|
failed = [task for task in store.tasks.values() if task.status == TaskStatus.FAILED]
|
|
done = [task for task in store.tasks.values() if task.status == TaskStatus.DONE]
|
|
|
|
self.assertEqual(len(failed), 1)
|
|
self.assertEqual(len(done), 2)
|
|
self.assertEqual(result.completed_tasks, 2)
|
|
self.assertEqual(store.shared_state[f"run:{run_id}:status"], "converged")
|
|
self.assertIn("healthy result", result.accepted_output)
|
|
self.assertTrue(any(obs.signal == "route:failed" for obs in result.observations))
|
|
self.assertTrue(any(obs.signal == "route:done" for obs in result.observations))
|
|
|
|
def test_emergent_consensus_accumulates_local_evidence(self) -> None:
|
|
store = InMemorySwarmStore()
|
|
run_id = "behavior-emergent-consensus"
|
|
store.shared_state[f"run:{run_id}:goal"] = "local evidence should create group consensus"
|
|
store.shared_state[f"run:{run_id}:status"] = "running"
|
|
for payload in [
|
|
"alpha:0.31",
|
|
"beta:0.33",
|
|
"beta:0.34",
|
|
"gamma:0.45",
|
|
]:
|
|
store.add_task(Task(kind="evidence", input=payload))
|
|
|
|
local_signals: list[float] = []
|
|
|
|
def contribute(task: Task, shared_state: dict[str, str]) -> tuple[str, float]:
|
|
candidate, value_text = task.input.split(":")
|
|
value = float(value_text)
|
|
local_signals.append(value)
|
|
key = f"candidate:{candidate}:score"
|
|
total = float(shared_state.get(key, "0")) + value
|
|
shared_state[key] = f"{total:.2f}"
|
|
shared_state[f"candidate:{candidate}:last_local_signal"] = f"{value:.2f}"
|
|
return f"candidate={candidate}; local={value:.2f}; group_total={total:.2f}", total
|
|
|
|
agents = [
|
|
Agent(id="local-evidence-a", capability="evidence", run=contribute),
|
|
Agent(id="local-evidence-b", capability="evidence", run=contribute),
|
|
Agent(id="local-evidence-c", capability="evidence", run=contribute),
|
|
]
|
|
|
|
result = SwarmCoordinator(store=store, agents=agents).run_until_converged(run_id)
|
|
|
|
self.assertEqual(result.completed_tasks, 4)
|
|
self.assertIn("candidate=beta", result.accepted_output)
|
|
self.assertEqual(store.shared_state["candidate:beta:score"], "0.67")
|
|
self.assertLess(max(value for value in local_signals if value < 0.45), 0.45)
|
|
self.assertGreater(result.accepted_score, 0.45)
|
|
self.assertEqual(store.shared_state[f"run:{run_id}:status"], "converged")
|
|
|
|
def test_pheromone_biases_claim_order_and_records_positive_feedback(self) -> None:
|
|
store = InMemorySwarmStore()
|
|
run_id = "behavior-pheromone"
|
|
store.shared_state[f"run:{run_id}:goal"] = "pheromone should bias task selection"
|
|
store.shared_state[f"run:{run_id}:status"] = "running"
|
|
low = Task(kind="probe", input="low-signal")
|
|
high = Task(kind="probe", input="high-signal")
|
|
medium = Task(kind="probe", input="medium-signal")
|
|
for task in [low, high, medium]:
|
|
store.add_task(task)
|
|
store.pheromones[low.id] = 0.1
|
|
store.pheromones[high.id] = 0.9
|
|
store.pheromones[medium.id] = 0.4
|
|
claim_order: list[str] = []
|
|
|
|
def run_probe(task: Task, _: dict[str, str]) -> tuple[str, float]:
|
|
claim_order.append(task.input)
|
|
score_by_input = {
|
|
"high-signal": 0.82,
|
|
"medium-signal": 0.62,
|
|
"low-signal": 0.31,
|
|
}
|
|
return f"processed {task.input}", score_by_input[task.input]
|
|
|
|
agents = [
|
|
Agent(id="probe-a", capability="probe", run=run_probe),
|
|
Agent(id="probe-b", capability="probe", run=run_probe),
|
|
]
|
|
|
|
result = SwarmCoordinator(store=store, agents=agents).run_until_converged(run_id)
|
|
|
|
self.assertEqual(claim_order[0], "high-signal")
|
|
self.assertIn("high-signal", result.accepted_output)
|
|
self.assertGreater(store.pheromones[high.id], store.pheromones[medium.id])
|
|
self.assertGreater(store.pheromones[medium.id], store.pheromones[low.id])
|
|
|
|
def test_langgraph_style_handoff_preserves_active_agent_and_payload(self) -> None:
|
|
store = InMemorySwarmStore()
|
|
run_id = "behavior-handoff"
|
|
store.shared_state[f"run:{run_id}:goal"] = "handoff continuity"
|
|
store.shared_state[f"run:{run_id}:status"] = "running"
|
|
store.shared_state[f"run:{run_id}:active_agent"] = "collector"
|
|
store.add_task(Task(kind="collector", input="collect code facts"))
|
|
store.add_task(Task(kind="analyst", input="analyze code facts"))
|
|
store.add_task(Task(kind="reporter", input="write final report"))
|
|
|
|
def handoff_tool_name(agent_name: str) -> str:
|
|
return f"transfer_to_{agent_name}"
|
|
|
|
def collector(_: Task, shared_state: dict[str, str]) -> tuple[str, float]:
|
|
self.assertEqual(shared_state[f"run:{run_id}:active_agent"], "collector")
|
|
shared_state[f"handoff:{run_id}:collector->analyst"] = handoff_tool_name("analyst")
|
|
shared_state[f"payload:{run_id}:analyst"] = "facts: task_pool pheromone shared_state convergence"
|
|
shared_state[f"run:{run_id}:active_agent"] = "analyst"
|
|
return "collector handed off to analyst", 0.42
|
|
|
|
def analyst(_: Task, shared_state: dict[str, str]) -> tuple[str, float]:
|
|
self.assertEqual(shared_state[f"run:{run_id}:active_agent"], "analyst")
|
|
self.assertIn("pheromone", shared_state[f"payload:{run_id}:analyst"])
|
|
shared_state[f"handoff:{run_id}:analyst->reporter"] = handoff_tool_name("reporter")
|
|
shared_state[f"payload:{run_id}:reporter"] = shared_state[f"payload:{run_id}:analyst"] + "; analysis: ready"
|
|
shared_state[f"run:{run_id}:active_agent"] = "reporter"
|
|
return "analyst handed off to reporter", 0.66
|
|
|
|
def reporter(_: Task, shared_state: dict[str, str]) -> tuple[str, float]:
|
|
self.assertEqual(shared_state[f"run:{run_id}:active_agent"], "reporter")
|
|
self.assertIn("analysis: ready", shared_state[f"payload:{run_id}:reporter"])
|
|
return "reporter final result from preserved handoff payload", 0.94
|
|
|
|
agents = [
|
|
Agent(id="collector", capability="collector", run=collector),
|
|
Agent(id="analyst", capability="analyst", run=analyst),
|
|
Agent(id="reporter", capability="reporter", run=reporter),
|
|
]
|
|
|
|
result = SwarmCoordinator(store=store, agents=agents).run_until_converged(run_id)
|
|
|
|
self.assertEqual(store.shared_state[f"handoff:{run_id}:collector->analyst"], "transfer_to_analyst")
|
|
self.assertEqual(store.shared_state[f"handoff:{run_id}:analyst->reporter"], "transfer_to_reporter")
|
|
self.assertIn("reporter final result", result.accepted_output)
|
|
self.assertEqual(result.completed_tasks, 3)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|