Task 14: coordinator-side model swap flow

- request_model_swap() publishes cmd.swap_model on jc.admin, sets node status to swapping
- handle_model_ready() updates active_model from inventory, restores active status
- handle_model_failed() sets node status to error with failure detail
- select_node() made async with two-pass logic: find ready node or trigger swap
- inventory field stored in node records for swap target lookup
- Both handlers subscribed on jc.system via SUBSCRIBE_TABLE
This commit is contained in:
gramps
2026-07-09 09:04:15 -07:00
parent fb0ff576d3
commit 9d1fd44d7f
5 changed files with 299 additions and 13 deletions
+10 -2
View File
@@ -1,4 +1,5 @@
"""Tests for triage.py — query classification and node selection."""
import asyncio
import json
import httpx
@@ -13,6 +14,13 @@ def _reset():
cluster.CLUSTER_COORDINATOR = None
_published = []
async def _fake_publish(exchange, routing_key, payload):
_published.append((exchange, routing_key, payload))
class _MockPostResponse:
def __init__(self, json_data: dict, status_code: int = 200):
self._json_data = json_data
@@ -83,7 +91,7 @@ def test_select_node_code_returns_coder():
"active_model": {"name": "llama3.1", "port": 8081},
}
node = triage.select_node("code")
node = asyncio.run(triage.select_node("code"))
assert node is not None
assert node["name"] == "coder01"
@@ -97,7 +105,7 @@ def test_select_node_general_no_match_returns_none():
"name": "coder01", "type": "worker", "status": "active",
"active_model": {"name": "qwen2.5-coder-14b", "port": 8082},
}
node = triage.select_node("general")
node = asyncio.run(triage.select_node("general"))
assert node is None