atlas-iac/services/hermes/scripts/execution_pool_client.py
Hermes Agent 7a55b259bf hermes: add the fenced three-node distributed execution pool
Three fenced worker Pods claim Hermes Kanban runs through a coordinator that
owns every state transition, with per-ordinal HMAC authority, a mediated
broker-only SCM path, and durable per-ordinal workspaces.

Content is the reviewed head of PR #18 (689bcb6e) with PR 16's and PR 19's
contributions removed: they were merged in only to validate co-existence and are
not prerequisites, so this branch no longer carries them as ancestors. Only PR 14
and PR 15 remain, because the broker boundary and the cli_lane_* decomposition
are load-bearing for two of the fixed P0 boundaries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 16:31:15 +00:00

222 lines
8.8 KiB
Python

#!/usr/bin/env python3
"""Ordinal-local signing and SCM mediation boundary for a model worker."""
from __future__ import annotations
import json
import os
import threading
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler
from pathlib import Path
from typing import Any
import cli_lane_goal
from execution_pool_protocol import (
MAX_WIRE_BYTES,
PROTOCOL_VERSION,
BoundedHTTPServer,
ProtocolError,
canonical_json,
parse_wire,
read_key,
sign_envelope,
verify_envelope,
)
from execution_pool_scm import Boundary as SCMBoundary
KEY_PATH = Path(
os.environ.get("HERMES_EXECUTION_POOL_KEY_FILE", "/pool-access/execution-pool-key")
)
COORDINATOR = os.environ.get(
"HERMES_EXECUTION_POOL_URL",
"http://hermes-execution-pool.hermes.svc.cluster.local:9007",
).rstrip("/")
ORDINAL = int(os.environ.get("HERMES_WORKER_ORDINAL", "-1"))
PORT = int(os.environ.get("HERMES_EXECUTION_CLIENT_PORT", "9009"))
RESULT_FIELDS = frozenset(
{"status", "summary", "changed_files", "tests_run", "artifacts", "findings", "blockers"}
)
def _binding(value: dict[str, Any]) -> dict[str, Any]:
return {
name: value[name]
for name in ("board", "task_id", "run_id", "worker_ordinal", "attempt")
}
def _validate_result(payload: Any) -> dict[str, Any]:
if not isinstance(payload, dict):
raise ProtocolError("terminal result payload must be an object")
structured = payload.get("structured")
if not isinstance(structured, dict) or set(structured) != RESULT_FIELDS:
raise ProtocolError("terminal result fields do not match the reviewed schema")
if structured.get("status") not in cli_lane_goal.RESULT_STATUSES:
raise ProtocolError("terminal result status is invalid")
if not isinstance(structured.get("summary"), str) or not structured["summary"].strip():
raise ProtocolError("terminal result summary is required")
for name in RESULT_FIELDS - {"status", "summary"}:
value = structured.get(name)
if not isinstance(value, list) or any(not isinstance(item, str) for item in value):
raise ProtocolError(f"terminal result {name} must be a text list")
return payload
class ClientBoundary:
"""Keep HMAC and SCM authority outside the model-facing container."""
def __init__(self, key: bytes, scm: SCMBoundary | None = None):
self.key = key
self.scm = scm or SCMBoundary(key)
self.current: dict[str, Any] | None = None
self.lock = threading.RLock()
def _post(self, path: str, envelope: dict[str, Any]) -> dict[str, Any]:
request = urllib.request.Request(
COORDINATOR + path,
data=canonical_json(envelope),
method="POST",
headers={"Content-Type": "application/json", "Cache-Control": "no-store"},
)
with urllib.request.urlopen(request, timeout=60) as response:
body = response.read(MAX_WIRE_BYTES + 1)
if len(body) > MAX_WIRE_BYTES:
raise ProtocolError("coordinator response exceeds the wire limit")
return verify_envelope(self.key, json.loads(body))
def poll(self) -> dict[str, Any]:
poll_binding = {
"board": "",
"task_id": "",
"run_id": "",
"worker_ordinal": ORDINAL,
"attempt": 0,
}
response = self._post(
"/v1/poll",
sign_envelope(self.key, "poll", poll_binding, {"ready": True}),
)
with self.lock:
if response["kind"] == "ack":
self.current = None
return {"assignment": None}
if response["kind"] != "assignment" or response["worker_ordinal"] != ORDINAL:
raise ProtocolError("coordinator returned a foreign assignment")
checkout = self.scm.checkout(response)
self.current = response
assignment = {
**_binding(response),
"payload": response["payload"],
"workspace": checkout["workspace"],
"baseline_sha": checkout["baseline_sha"],
"protocol_version": PROTOCOL_VERSION,
}
return {"assignment": assignment}
def _current_for(self, supplied: Any) -> tuple[dict[str, Any], dict[str, Any]]:
if not isinstance(supplied, dict):
raise ProtocolError("local request binding must be an object")
if self.current is None or supplied != _binding(self.current):
raise ProtocolError("local request does not own the current assignment")
return self.current, dict(supplied)
def heartbeat(self, request: dict[str, Any]) -> dict[str, Any]:
payload = request.get("payload")
if not isinstance(payload, dict):
raise ProtocolError("heartbeat payload must be an object")
with self.lock:
_assignment, binding = self._current_for(request.get("binding"))
response = self._post(
"/v1/heartbeat",
sign_envelope(self.key, "heartbeat", binding, payload),
)
if response["kind"] != "ack" or _binding(response) != binding:
raise ProtocolError("coordinator acknowledgement binding changed")
return {"ack": response["payload"]}
def finish(self, request: dict[str, Any]) -> dict[str, Any]:
payload = _validate_result(request.get("payload"))
with self.lock:
assignment, binding = self._current_for(request.get("binding"))
structured = payload["structured"]
if structured["status"] == "completed" and int(payload.get("returncode", 1)) == 0:
submission = self.scm.submit(assignment, request)
pull = str(submission.get("pull_request") or "")
if pull and pull not in structured["artifacts"]:
structured["artifacts"].append(pull)
response = self._post(
"/v1/result", sign_envelope(self.key, "result", binding, payload)
)
if response["kind"] != "ack" or _binding(response) != binding:
raise ProtocolError("coordinator acknowledgement binding changed")
if response["payload"].get("accepted"):
self.current = None
return {"ack": response["payload"], "structured": structured}
def handler_factory(boundary: ClientBoundary) -> type[BaseHTTPRequestHandler]:
class Handler(BaseHTTPRequestHandler):
server_version = f"hermes-execution-mediator/{PROTOCOL_VERSION}"
def _reply(self, status: int, value: dict[str, Any]) -> None:
body = canonical_json(value)
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
def do_GET(self) -> None: # noqa: N802
value = {"ready": True, "protocol_version": PROTOCOL_VERSION}
self._reply(200, value) if self.path == "/ready" else self._reply(
404, {"error": "not found"}
)
def do_POST(self) -> None: # noqa: N802
try:
length = int(self.headers.get("Content-Length", "0"))
request = (
parse_wire(self.rfile.read(length))
if 0 < length <= MAX_WIRE_BYTES
else None
)
allowed = {"operation", "binding", "payload", "title", "body"}
if not request or set(request) - allowed:
raise ProtocolError("invalid local mediator request")
operation = str(request.get("operation") or "")
routes = {
"poll": boundary.poll,
"heartbeat": lambda: boundary.heartbeat(request),
"finish": lambda: boundary.finish(request),
}
if operation not in routes:
raise ProtocolError("unsupported local mediator operation")
self._reply(200, routes[operation]())
except (ProtocolError, OSError, ValueError, urllib.error.URLError) as error:
self._reply(409, {"error": str(error)[:2000]})
def log_message(self, _format: str, *_arguments: Any) -> None:
return
return Handler
def main() -> int:
if ORDINAL not in range(3):
raise SystemExit("HERMES_WORKER_ORDINAL must be 0, 1, or 2")
key = read_key(KEY_PATH)
BoundedHTTPServer(
("0.0.0.0", PORT),
handler_factory(ClientBoundary(key)),
max_workers=4,
).serve_forever()
return 0
if __name__ == "__main__": # pragma: no cover - exercised by the container entrypoint
raise SystemExit(main())