mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-04 07:31:58 +00:00
feat(cli): add kanban swarm topology helper
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.
This commit is contained in:
parent
79f6654d16
commit
3ee7a5546d
3 changed files with 451 additions and 0 deletions
118
tests/hermes_cli/test_kanban_swarm.py
Normal file
118
tests/hermes_cli/test_kanban_swarm.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue