feat(intelligent-eval): platform triggers OpenClaw agent as worker (avoid external IM channel)
All checks were successful
CI / test (push) Successful in 3m48s

OpenClaw cron requires a channel (announce->last fail-closed); webchat is a
Control-UI feature, not an addressable channel, and platform-side static
execution would degrade the intelligent eval into a static evaluation.

Solution (plan C): the platform keeps the scan loop and, when the queue has
a pending task, invokes the headless agent:
  docker exec openclaw-eval openclaw agent --agent main \
    -m agenteval-intelligent-worker --json
--deliver defaults to false, so no cron delivery channel is involved. The
worker skill runs unchanged under the OpenClaw agent (LLM decisions +
evaluator/analyst skills). Verified headless invocation returns ok.
This commit is contained in:
sinohqb 2026-08-17 02:37:37 +08:00
parent 5836b84681
commit 775b070bab
2 changed files with 102 additions and 3 deletions

View File

@ -31,12 +31,61 @@ from agenteval.web.routers import (
from agenteval.web.websocket import ws_manager from agenteval.web.websocket import ws_manager
def _has_pending_task() -> bool:
"""True if the intelligent-eval task queue has a pending worker task."""
from agenteval.intelligent_eval.task_queue import get_next_task
session = get_session()
try:
return get_next_task(session) is not None
finally:
session.close()
async def _trigger_intelligent_worker() -> bool:
"""Trigger OpenClaw's headless agent to run the intelligent-eval worker skill.
方案③避免外部 IM channel平台只当"触发闹钟" pending 任务时
`docker exec openclaw-eval openclaw agent --agent main -m
"agenteval-intelligent-worker"` 唤醒 agentworker skill OpenClaw agent
自主执行决策/建会话/真实对话/上报逻辑零改动`--deliver` 默认 false
因此不经 cron delivery channel
Returns:
True 若确实触发了 agent存在 pending 任务
"""
if not _has_pending_task():
return False
import subprocess
_logger = logging.getLogger("agenteval")
try:
proc = await asyncio.to_thread(
subprocess.run,
[
"docker", "exec", "openclaw-eval", "openclaw", "agent",
"--agent", "main", "-m", "agenteval-intelligent-worker", "--json",
],
capture_output=True,
text=True,
timeout=300,
)
_logger.info("Worker 触发完成 exit=%s", proc.returncode)
if proc.returncode != 0:
_logger.warning("Worker 触发 stderr: %s", proc.stderr[-300:])
except Exception as exc:
_logger.warning("Worker 触发失败(忽略): %s", exc)
return True
async def _intelligent_eval_scan_loop() -> None: async def _intelligent_eval_scan_loop() -> None:
"""Scan executing intelligent evals and enqueue worker tasks every minute. """Scan executing intelligent evals, enqueue tasks, and trigger the worker.
v1.1.0 缺陷修复`scan_and_enqueue_tasks` 此前没有调度点OpenClaw Worker v1.1.0 缺陷修复`scan_and_enqueue_tasks` 此前没有调度点OpenClaw Worker
每分钟唤醒却永远取不到任务平台启动后每 60s 扫描一次 executing 的评估 每分钟唤醒却永远取不到任务平台启动后每 60s 扫描 executing 的评估入队
Worker 的每分钟唤醒对齐失败不阻断下次循环继续 若有 pending 任务则触发 OpenClaw agent 执行 worker skill方案③免外部
channel失败不阻断下次循环继续
""" """
_logger = logging.getLogger("agenteval") _logger = logging.getLogger("agenteval")
while True: while True:
@ -52,6 +101,10 @@ async def _intelligent_eval_scan_loop() -> None:
session.close() session.close()
except Exception as exc: except Exception as exc:
logging.getLogger("agenteval").warning("智能评估扫描失败(忽略): %s", exc) logging.getLogger("agenteval").warning("智能评估扫描失败(忽略): %s", exc)
try:
await _trigger_intelligent_worker()
except Exception as exc:
logging.getLogger("agenteval").warning("Worker 触发失败(忽略): %s", exc)
await asyncio.sleep(60) await asyncio.sleep(60)

View File

@ -32,3 +32,49 @@ def test_lifespan_starts_scan_loop(monkeypatch):
# The background task runs immediately (before its first 60s sleep). # The background task runs immediately (before its first 60s sleep).
assert calls, "scan_and_enqueue_tasks should have been invoked on startup" assert calls, "scan_and_enqueue_tasks should have been invoked on startup"
def test_trigger_worker_skips_when_no_pending(monkeypatch):
"""No pending task → no docker exec invocation."""
import asyncio
import subprocess
from unittest.mock import MagicMock
import agenteval.web.app as app_mod
calls: list = []
monkeypatch.setattr(app_mod, "_has_pending_task", lambda: False)
def fake_run(cmd, **kwargs):
calls.append(cmd)
return MagicMock(returncode=0, stderr="")
monkeypatch.setattr(subprocess, "run", fake_run)
assert asyncio.run(app_mod._trigger_intelligent_worker()) is False
assert not calls, "should not invoke docker exec when queue is empty"
def test_trigger_worker_calls_docker_exec(monkeypatch):
"""Pending task present → invoke `docker exec openclaw-eval openclaw agent`."""
import asyncio
import subprocess
from unittest.mock import MagicMock
import agenteval.web.app as app_mod
calls: list = []
monkeypatch.setattr(app_mod, "_has_pending_task", lambda: True)
def fake_run(cmd, **kwargs):
calls.append(cmd)
return MagicMock(returncode=0, stderr="")
monkeypatch.setattr(subprocess, "run", fake_run)
assert asyncio.run(app_mod._trigger_intelligent_worker()) is True
assert calls, "docker exec should be invoked"
joined = " ".join(calls[0])
assert "docker" in joined and "openclaw" in joined
assert "agenteval-intelligent-worker" in joined
assert "--agent" in joined and "main" in joined