mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-30 06:41:51 +00:00
Salvages #26791 by @Niraven. Adds 'hermes kanban swarm' to create a durable Kanban Swarm v1 graph: a completed root/blackboard card, parallel worker cards, a verifier gated on all workers, and a synthesizer gated on the verifier. Stores shared swarm blackboard updates as structured JSON comments on the root card. Self-contained: new hermes_cli/kanban_swarm.py module + CLI wiring + unit tests.
118 lines
4.1 KiB
Python
118 lines
4.1 KiB
Python
import json
|
|
|
|
from hermes_cli import kanban_db as kb
|
|
from hermes_cli.kanban_swarm import (
|
|
SwarmWorkerSpec,
|
|
create_swarm,
|
|
latest_blackboard,
|
|
post_blackboard_update,
|
|
)
|
|
|
|
|
|
def test_create_swarm_builds_parallel_workers_verifier_and_synthesizer(tmp_path):
|
|
conn = kb.connect(tmp_path / "kanban.db")
|
|
try:
|
|
created = create_swarm(
|
|
conn,
|
|
goal="Map the target market and produce a decision memo.",
|
|
workers=[
|
|
SwarmWorkerSpec(profile="researcher-a", title="Market scan", body="Find competitors"),
|
|
SwarmWorkerSpec(profile="researcher-b", title="Customer scan", body="Find customer pains"),
|
|
],
|
|
verifier_assignee="reviewer",
|
|
synthesizer_assignee="writer",
|
|
tenant="intel",
|
|
created_by="orchestrator",
|
|
)
|
|
|
|
root = kb.get_task(conn, created.root_id)
|
|
workers = [kb.get_task(conn, tid) for tid in created.worker_ids]
|
|
verifier = kb.get_task(conn, created.verifier_id)
|
|
synthesizer = kb.get_task(conn, created.synthesizer_id)
|
|
|
|
assert root.status == "done"
|
|
assert root.assignee == "orchestrator"
|
|
assert [task.status for task in workers] == ["ready", "ready"]
|
|
assert [task.assignee for task in workers] == ["researcher-a", "researcher-b"]
|
|
assert verifier.status == "todo"
|
|
assert synthesizer.status == "todo"
|
|
assert set(kb.parent_ids(conn, created.verifier_id)) == set(created.worker_ids)
|
|
assert kb.parent_ids(conn, created.synthesizer_id) == [created.verifier_id]
|
|
assert all(created.root_id in (task.body or "") for task in workers)
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_swarm_blackboard_merges_structured_updates(tmp_path):
|
|
conn = kb.connect(tmp_path / "kanban.db")
|
|
try:
|
|
created = create_swarm(
|
|
conn,
|
|
goal="Collect evidence.",
|
|
workers=[SwarmWorkerSpec(profile="researcher", title="Evidence", body="Find proof")],
|
|
verifier_assignee="reviewer",
|
|
synthesizer_assignee="writer",
|
|
)
|
|
|
|
post_blackboard_update(
|
|
conn,
|
|
created.root_id,
|
|
author="researcher",
|
|
key="sources",
|
|
value=["https://example.com/a"],
|
|
)
|
|
post_blackboard_update(
|
|
conn,
|
|
created.root_id,
|
|
author="reviewer",
|
|
key="risks",
|
|
value={"missing_primary_source": True},
|
|
)
|
|
|
|
board = latest_blackboard(conn, created.root_id)
|
|
assert board["sources"] == ["https://example.com/a"]
|
|
assert board["risks"] == {"missing_primary_source": True}
|
|
assert board["_authors"]["sources"] == "researcher"
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def test_swarm_verifier_and_synthesis_are_dependency_gated(tmp_path):
|
|
conn = kb.connect(tmp_path / "kanban.db")
|
|
try:
|
|
created = create_swarm(
|
|
conn,
|
|
goal="Research two branches then verify and synthesize.",
|
|
workers=[
|
|
SwarmWorkerSpec(profile="a", title="Branch A", body="A"),
|
|
SwarmWorkerSpec(profile="b", title="Branch B", body="B"),
|
|
],
|
|
verifier_assignee="reviewer",
|
|
synthesizer_assignee="writer",
|
|
)
|
|
|
|
kb.complete_task(
|
|
conn,
|
|
created.worker_ids[0],
|
|
summary="A done",
|
|
metadata={"confidence": 0.8},
|
|
)
|
|
kb.recompute_ready(conn)
|
|
assert kb.get_task(conn, created.verifier_id).status == "todo"
|
|
assert kb.get_task(conn, created.synthesizer_id).status == "todo"
|
|
|
|
kb.complete_task(conn, created.worker_ids[1], summary="B done")
|
|
kb.recompute_ready(conn)
|
|
assert kb.get_task(conn, created.verifier_id).status == "ready"
|
|
assert kb.get_task(conn, created.synthesizer_id).status == "todo"
|
|
|
|
kb.complete_task(
|
|
conn,
|
|
created.verifier_id,
|
|
summary="Verified both branches",
|
|
metadata={"gate": "pass"},
|
|
)
|
|
kb.recompute_ready(conn)
|
|
assert kb.get_task(conn, created.synthesizer_id).status == "ready"
|
|
finally:
|
|
conn.close()
|