Task 007 implement agent job queue and fake runner path

This commit is contained in:
2026-05-21 19:28:37 +03:00
parent ff1a2f78c0
commit 4fd837349f
12 changed files with 956 additions and 7 deletions
+118
View File
@@ -0,0 +1,118 @@
from __future__ import annotations
import json
import shutil
import time
from pathlib import Path
from typing import Any, Protocol
from urllib import request
class BackendJobClient(Protocol):
def claim_job(self) -> dict[str, Any] | None:
pass
def complete_job(self, job_id: str, payload: dict[str, Any]) -> dict[str, Any]:
pass
class HttpBackendJobClient:
def __init__(self, backend_url: str) -> None:
self.backend_url = backend_url.rstrip("/")
def claim_job(self) -> dict[str, Any] | None:
response = self._post_json("/internal/agent-jobs/claim", None)
if response is None:
return None
return response["job"]
def complete_job(self, job_id: str, payload: dict[str, Any]) -> dict[str, Any]:
response = self._post_json(f"/internal/agent-jobs/{job_id}/complete", payload)
if response is None:
raise RuntimeError("Backend returned empty completion response")
return response["job"]
def _post_json(self, path: str, payload: dict[str, Any] | None) -> dict[str, Any] | None:
data = None if payload is None else json.dumps(payload).encode("utf-8")
http_request = request.Request(
f"{self.backend_url}{path}",
data=data,
method="POST",
headers={"Content-Type": "application/json"},
)
try:
with request.urlopen(http_request, timeout=5) as response:
if response.status == 204:
return None
body = response.read().decode("utf-8")
except Exception as error:
raise RuntimeError(f"Backend request failed: {path}") from error
if not body:
return None
return json.loads(body)
def run_fake_runner_once(
backend_client: BackendJobClient,
*,
workspace_root: Path,
) -> dict[str, Any] | None:
job = backend_client.claim_job()
if job is None:
return None
started = time.monotonic()
workspace_path = materialize_fake_workspace(job, workspace_root=workspace_root)
stdout = "fake Codex runner completed\n"
stderr = ""
output: dict[str, Any]
if job["agent_profile"] == "fake-invalid-schema":
output = {"status": "NOT_A_STATUS"}
else:
output = {
"status": "SUCCEEDED",
"output_files": [{"path": "outputs/result.json"}],
"payload": {"job_id": job["id"], "mode": "fake"},
}
duration_ms = int((time.monotonic() - started) * 1000)
return backend_client.complete_job(
job["id"],
{
"workspace_path": str(workspace_path),
"stdout": stdout,
"stderr": stderr,
"exit_code": 0,
"duration_ms": duration_ms,
"output": output,
},
)
def materialize_fake_workspace(
job: dict[str, Any],
*,
workspace_root: Path,
) -> Path:
workspace_path = workspace_root / job["id"]
if workspace_path.exists():
shutil.rmtree(workspace_path)
(workspace_path / "inputs").mkdir(parents=True)
(workspace_path / "logs").mkdir()
(workspace_path / "outputs").mkdir()
(workspace_path / "inputs" / "job.json").write_text(
json.dumps(job, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
(workspace_path / "logs" / "stdout.log").write_text(
"fake Codex runner completed\n",
encoding="utf-8",
)
(workspace_path / "logs" / "stderr.log").write_text("", encoding="utf-8")
(workspace_path / "outputs" / "result.json").write_text(
json.dumps({"status": "ok", "job_id": job["id"]}, sort_keys=True) + "\n",
encoding="utf-8",
)
return workspace_path
+18
View File
@@ -2,9 +2,12 @@ from __future__ import annotations
import json
import os
import threading
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from src.application.fake_runner import get_backend_url, get_workspace_root, run_fake_runner_loop
class HealthHandler(BaseHTTPRequestHandler):
def do_GET(self) -> None:
@@ -24,6 +27,21 @@ class HealthHandler(BaseHTTPRequestHandler):
def run() -> None:
mode = os.environ.get("RUNNER_MODE", "fake").lower()
if mode == "fake":
thread = threading.Thread(
target=run_fake_runner_loop,
kwargs={
"backend_url": get_backend_url(),
"workspace_root": get_workspace_root(),
"poll_interval_seconds": float(
os.environ.get("RUNNER_POLL_INTERVAL_SECONDS", "1.0")
),
},
daemon=True,
)
thread.start()
port = int(os.environ.get("PORT", "8010"))
server = ThreadingHTTPServer(("0.0.0.0", port), HealthHandler)
server.serve_forever()