ref(heartbeat): Remove cron provisioning

Remove gateway cron helpers and startup provisioning, relying on\nheartbeat instructions instead. Also drop unused httpx dependency.\n\nCo-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Abhimanyu Saharan
2026-02-04 15:44:21 +05:30
parent 4979991de6
commit 12698d0781
6 changed files with 2 additions and 190 deletions

View File

@@ -4,10 +4,9 @@ import asyncio
import json
from dataclasses import dataclass
from typing import Any
from urllib.parse import quote, urlencode, urlparse, urlunparse
from urllib.parse import urlencode, urlparse, urlunparse
from uuid import uuid4
import httpx
import websockets
from app.core.config import settings
@@ -32,54 +31,6 @@ def _build_gateway_url() -> str:
return urlunparse(parsed._replace(query=query))
def _build_gateway_http_url() -> str:
base_url = settings.openclaw_gateway_url or "ws://127.0.0.1:18789"
parsed = urlparse(base_url)
if parsed.scheme in {"http", "https"}:
scheme = parsed.scheme
elif parsed.scheme == "wss":
scheme = "https"
else:
scheme = "http"
return urlunparse(
parsed._replace(scheme=scheme, path="", params="", query="", fragment="")
)
def _gateway_headers() -> dict[str, str]:
headers: dict[str, str] = {}
if settings.openclaw_gateway_token:
headers["Authorization"] = f"Bearer {settings.openclaw_gateway_token}"
return headers
async def _http_request(method: str, path: str, payload: dict[str, Any] | None = None) -> Any:
base_url = _build_gateway_http_url().rstrip("/")
url = f"{base_url}{path}"
try:
async with httpx.AsyncClient(timeout=10) as client:
response = await client.request(
method, url, json=payload, headers=_gateway_headers()
)
if response.status_code >= 400:
raise OpenClawGatewayError(
f"{response.status_code}: {response.text or 'Gateway error'}"
)
if not response.content:
return None
try:
return response.json()
except ValueError as exc:
preview = response.text.strip()[:200] or "<empty>"
raise OpenClawGatewayError(
f"Non-JSON response from gateway: {preview}"
) from exc
except OpenClawGatewayError:
raise
except Exception as exc: # pragma: no cover - transport errors
raise OpenClawGatewayError(str(exc)) from exc
async def _await_response(ws: websockets.WebSocketClientProtocol, request_id: str) -> Any:
while True:
raw = await ws.recv()
@@ -187,24 +138,3 @@ async def ensure_session(session_key: str, label: str | None = None) -> Any:
params["label"] = label
return await openclaw_call("sessions.patch", params)
async def list_cron_jobs() -> Any:
try:
return await _http_request("GET", "/api/v1/cron/jobs")
except OpenClawGatewayError:
return await _http_request("GET", "/cron/jobs")
async def upsert_cron_job(job: dict[str, Any]) -> Any:
try:
return await _http_request("POST", "/api/v1/cron/jobs", payload=job)
except OpenClawGatewayError:
return await _http_request("POST", "/cron/jobs", payload=job)
async def delete_cron_job(name: str) -> Any:
safe_name = quote(name, safe="")
try:
return await _http_request("DELETE", f"/api/v1/cron/jobs/{safe_name}")
except OpenClawGatewayError:
return await _http_request("DELETE", f"/cron/jobs/{safe_name}")

View File

@@ -12,7 +12,6 @@ from app.api.tasks import router as tasks_router
from app.core.config import settings
from app.core.logging import configure_logging
from app.db.session import init_db
from app.services.cron_jobs import ensure_mission_control_cron_job
configure_logging()
@@ -30,9 +29,8 @@ if origins:
@app.on_event("startup")
async def on_startup() -> None:
def on_startup() -> None:
init_db()
await ensure_mission_control_cron_job()
@app.get("/health")

View File

@@ -1,65 +0,0 @@
from __future__ import annotations
import logging
from typing import Any
from app.integrations.openclaw_gateway import (
OpenClawGatewayError,
list_cron_jobs,
upsert_cron_job,
)
logger = logging.getLogger(__name__)
MISSION_CONTROL_CRON_NAME = "mission-control-runner/10m"
def _mission_control_runner_message() -> str:
return (
"You are the Mission Control Runner agent.\n\n"
"On this scheduled tick:\n"
"- Run the HEARTBEAT.md procedure for Mission Control (check-in, list boards, "
"list tasks).\n"
"- If any task is already in_progress, stop (do not claim another).\n"
"- Otherwise, find the oldest inbox task across all boards, claim it by moving "
"to in_progress.\n"
"- Execute the task fully.\n"
"- When complete, move it to review.\n"
"- If no inbox tasks exist, do nothing.\n"
"Only update Mission Control (no chat messages)."
)
def build_mission_control_cron_job() -> dict[str, Any]:
return {
"name": MISSION_CONTROL_CRON_NAME,
"schedule": {"kind": "every", "everyMs": 600000},
"sessionTarget": "isolated",
"enabled": True,
"payload": {"kind": "agentTurn", "message": _mission_control_runner_message()},
}
async def ensure_mission_control_cron_job() -> None:
try:
payload = await list_cron_jobs()
except OpenClawGatewayError as exc:
logger.warning("Gateway cron list failed: %s", exc)
return
jobs: list[dict[str, Any]] = []
if isinstance(payload, list):
jobs = payload
elif isinstance(payload, dict):
jobs = list(payload.get("jobs", []))
job = build_mission_control_cron_job()
if any(item.get("name") == job["name"] for item in jobs):
logger.info("Updating gateway cron job: %s", job["name"])
else:
logger.info("Creating gateway cron job: %s", job["name"])
try:
await upsert_cron_job(job)
except OpenClawGatewayError as exc:
logger.warning("Gateway cron upsert failed: %s", exc)

View File

@@ -20,7 +20,6 @@ dependencies = [
"psycopg[binary]==3.2.1",
"pydantic-settings==2.5.2",
"python-dotenv==1.0.1",
"httpx==0.27.2",
"websockets==12.0",
"rq==1.16.2",
"redis==5.1.1",

View File

@@ -6,7 +6,6 @@ alembic==1.13.2
psycopg[binary]==3.2.1
pydantic-settings==2.5.2
python-dotenv==1.0.1
httpx==0.27.2
websockets==12.0
rq==1.16.2
redis==5.1.1

49
backend/uv.lock generated
View File

@@ -62,15 +62,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898, upload-time = "2024-10-07T19:20:48.317Z" },
]
[[package]]
name = "certifi"
version = "2026.1.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
]
[[package]]
name = "cffi"
version = "2.0.0"
@@ -268,19 +259,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httptools"
version = "0.7.1"
@@ -310,22 +288,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" },
]
[[package]]
name = "httpx"
version = "0.27.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" },
]
[[package]]
name = "idna"
version = "3.11"
@@ -472,7 +434,6 @@ dependencies = [
{ name = "alembic" },
{ name = "fastapi" },
{ name = "fastapi-clerk-auth" },
{ name = "httpx" },
{ name = "psycopg", extra = ["binary"] },
{ name = "pydantic-settings" },
{ name = "python-dotenv" },
@@ -503,7 +464,6 @@ requires-dist = [
{ name = "fastapi", specifier = "==0.115.4" },
{ name = "fastapi-clerk-auth", specifier = "==0.0.9" },
{ name = "flake8", marker = "extra == 'dev'", specifier = "==7.1.1" },
{ name = "httpx", specifier = "==0.27.2" },
{ name = "isort", marker = "extra == 'dev'", specifier = "==5.13.2" },
{ name = "mypy", marker = "extra == 'dev'", specifier = "==1.11.2" },
{ name = "psycopg", extras = ["binary"], specifier = "==3.2.1" },
@@ -858,15 +818,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/14/fd026bc74ded05e2351681545a5f626e78ef831f8edce064d61acd2e6ec7/ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", size = 8679879, upload-time = "2024-10-04T13:40:25.797Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "sqlalchemy"
version = "2.0.34"