Merge pull request #40 from AlexanderWhitestone/kimi/phase2-swarm-hardening-v2

Phase 2: Swarm hardening, auto-auction, WebSocket fix
This commit is contained in:
Alexander Whitestone
2026-02-25 17:34:13 -05:00
committed by GitHub
6 changed files with 229 additions and 76 deletions

View File

@@ -4,6 +4,7 @@ Provides REST endpoints for managing the swarm: listing agents,
spawning sub-agents, posting tasks, and viewing auction results.
"""
import asyncio
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
@@ -90,8 +91,10 @@ async def list_tasks(status: Optional[str] = None):
@router.post("/tasks")
async def post_task(description: str = Form(...)):
"""Post a new task to the swarm for bidding."""
"""Post a new task to the swarm and run auction to assign it."""
task = coordinator.post_task(description)
# Start auction asynchronously - don't wait for it to complete
asyncio.create_task(coordinator.run_auction_and_assign(task.id))
return {
"task_id": task.id,
"description": task.description,

View File

@@ -30,6 +30,7 @@
<a href="/marketplace/ui" class="mc-test-link">MARKET</a>
<a href="/tools" class="mc-test-link">TOOLS</a>
<a href="/creative/ui" class="mc-test-link">CREATIVE</a>
<a href="/mobile" class="mc-test-link" title="Mobile-optimized view">MOBILE</a>
<button id="enable-notifications" class="mc-test-link" style="background:none;cursor:pointer;" title="Enable notifications">&#x1F514;</button>
<span class="mc-time" id="clock"></span>
</div>

View File

@@ -367,7 +367,7 @@ class SwarmCoordinator:
async def _broadcast_agent_joined(self, agent_id: str, name: str) -> None:
"""Broadcast agent joined event via WebSocket."""
try:
from websocket.handler import ws_manager
from ws_manager.handler import ws_manager
await ws_manager.broadcast_agent_joined(agent_id, name)
except Exception as exc:
logger.debug("WebSocket broadcast failed (agent_joined): %s", exc)
@@ -375,7 +375,7 @@ class SwarmCoordinator:
async def _broadcast_bid(self, task_id: str, agent_id: str, bid_sats: int) -> None:
"""Broadcast bid submitted event via WebSocket."""
try:
from websocket.handler import ws_manager
from ws_manager.handler import ws_manager
await ws_manager.broadcast_bid_submitted(task_id, agent_id, bid_sats)
except Exception as exc:
logger.debug("WebSocket broadcast failed (bid): %s", exc)
@@ -383,7 +383,7 @@ class SwarmCoordinator:
async def _broadcast_task_posted(self, task_id: str, description: str) -> None:
"""Broadcast task posted event via WebSocket."""
try:
from websocket.handler import ws_manager
from ws_manager.handler import ws_manager
await ws_manager.broadcast_task_posted(task_id, description)
except Exception as exc:
logger.debug("WebSocket broadcast failed (task_posted): %s", exc)
@@ -391,7 +391,7 @@ class SwarmCoordinator:
async def _broadcast_task_assigned(self, task_id: str, agent_id: str) -> None:
"""Broadcast task assigned event via WebSocket."""
try:
from websocket.handler import ws_manager
from ws_manager.handler import ws_manager
await ws_manager.broadcast_task_assigned(task_id, agent_id)
except Exception as exc:
logger.debug("WebSocket broadcast failed (task_assigned): %s", exc)
@@ -401,7 +401,7 @@ class SwarmCoordinator:
) -> None:
"""Broadcast task completed event via WebSocket."""
try:
from websocket.handler import ws_manager
from ws_manager.handler import ws_manager
await ws_manager.broadcast_task_completed(task_id, agent_id, result)
except Exception as exc:
logger.debug("WebSocket broadcast failed (task_completed): %s", exc)

View File

@@ -15,21 +15,8 @@ from typing import Optional
DB_PATH = Path("data/swarm.db")
@dataclass
class AgentRecord:
id: str = field(default_factory=lambda: str(uuid.uuid4()))
name: str = ""
status: str = "idle" # idle | busy | offline
capabilities: str = "" # comma-separated tags
registered_at: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
last_seen: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
def _get_conn() -> sqlite3.Connection:
"""Get a SQLite connection."""
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(DB_PATH))
conn.row_factory = sqlite3.Row
@@ -49,6 +36,20 @@ def _get_conn() -> sqlite3.Connection:
return conn
@dataclass
class AgentRecord:
id: str = field(default_factory=lambda: str(uuid.uuid4()))
name: str = ""
status: str = "idle" # idle | busy | offline
capabilities: str = "" # comma-separated tags
registered_at: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
last_seen: str = field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
def _row_to_record(row: sqlite3.Row) -> AgentRecord:
return AgentRecord(
id=row["id"],
@@ -67,70 +68,81 @@ def register(name: str, capabilities: str = "", agent_id: Optional[str] = None)
capabilities=capabilities,
)
conn = _get_conn()
conn.execute(
"""
INSERT OR REPLACE INTO agents (id, name, status, capabilities, registered_at, last_seen)
VALUES (?, ?, ?, ?, ?, ?)
""",
(record.id, record.name, record.status, record.capabilities,
record.registered_at, record.last_seen),
)
conn.commit()
conn.close()
try:
conn.execute(
"""
INSERT OR REPLACE INTO agents (id, name, status, capabilities, registered_at, last_seen)
VALUES (?, ?, ?, ?, ?, ?)
""",
(record.id, record.name, record.status, record.capabilities,
record.registered_at, record.last_seen),
)
conn.commit()
finally:
conn.close()
return record
def unregister(agent_id: str) -> bool:
conn = _get_conn()
cursor = conn.execute("DELETE FROM agents WHERE id = ?", (agent_id,))
conn.commit()
deleted = cursor.rowcount > 0
conn.close()
return deleted
try:
cursor = conn.execute("DELETE FROM agents WHERE id = ?", (agent_id,))
conn.commit()
return cursor.rowcount > 0
finally:
conn.close()
def get_agent(agent_id: str) -> Optional[AgentRecord]:
conn = _get_conn()
row = conn.execute("SELECT * FROM agents WHERE id = ?", (agent_id,)).fetchone()
conn.close()
return _row_to_record(row) if row else None
try:
row = conn.execute("SELECT * FROM agents WHERE id = ?", (agent_id,)).fetchone()
return _row_to_record(row) if row else None
finally:
conn.close()
def list_agents(status: Optional[str] = None) -> list[AgentRecord]:
conn = _get_conn()
if status:
rows = conn.execute(
"SELECT * FROM agents WHERE status = ? ORDER BY registered_at DESC",
(status,),
).fetchall()
else:
rows = conn.execute(
"SELECT * FROM agents ORDER BY registered_at DESC"
).fetchall()
conn.close()
return [_row_to_record(r) for r in rows]
try:
if status:
rows = conn.execute(
"SELECT * FROM agents WHERE status = ? ORDER BY registered_at DESC",
(status,),
).fetchall()
else:
rows = conn.execute(
"SELECT * FROM agents ORDER BY registered_at DESC"
).fetchall()
return [_row_to_record(r) for r in rows]
finally:
conn.close()
def update_status(agent_id: str, status: str) -> Optional[AgentRecord]:
now = datetime.now(timezone.utc).isoformat()
conn = _get_conn()
conn.execute(
"UPDATE agents SET status = ?, last_seen = ? WHERE id = ?",
(status, now, agent_id),
)
conn.commit()
conn.close()
return get_agent(agent_id)
try:
conn.execute(
"UPDATE agents SET status = ?, last_seen = ? WHERE id = ?",
(status, now, agent_id),
)
conn.commit()
return get_agent(agent_id)
finally:
conn.close()
def heartbeat(agent_id: str) -> Optional[AgentRecord]:
"""Update last_seen timestamp for a registered agent."""
now = datetime.now(timezone.utc).isoformat()
conn = _get_conn()
conn.execute(
"UPDATE agents SET last_seen = ? WHERE id = ?",
(now, agent_id),
)
conn.commit()
conn.close()
return get_agent(agent_id)
try:
conn.execute(
"UPDATE agents SET last_seen = ? WHERE id = ?",
(now, agent_id),
)
conn.commit()
return get_agent(agent_id)
finally:
conn.close()