Synthesize playbooks from structured prompts and inject prior-run context.

Legacy jobs with empty recipes (e.g. dying-gasp algorithms in prompt_text) now compile into durable playbooks on create/enqueue, and each run gets a short previous-run summary for continuity.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-10 23:59:27 +08:00
parent 4898adf7d0
commit 51eef09801
8 changed files with 516 additions and 27 deletions

View file

@ -104,6 +104,46 @@ class RecipeHelpersTests(unittest.TestCase):
self.assertIn("工作流", scheduled_turn_system_suffix(lang="zh", playbook=True))
self.assertIn("提醒", scheduled_turn_system_suffix(lang="zh", playbook=False))
def test_synthesize_step_headers_and_previous_run(self) -> None:
from runtime.scheduler.recipe import synthesize_recipe_from_prompt
from runtime.scheduler.turn_text import append_previous_run_context
prompt = (
'Run "unmanaged by dying gasp" report.\n\n'
"CRITICAL — Follow this exact algorithm:\n\n"
"Step 1 — Query BN EMS failures:\n"
"- Query active alarms: native_probable_cause = \"BN EMS alarm NE communication failure\"\n\n"
"Step 2 — Query Remote dying gasp:\n"
"- Query active alarms: native_probable_cause LIKE \"%Remote dying gasp%\"\n\n"
"Step 3 — Join and deliver XLSX via save_deliverable_attachment.\n"
)
recipe = synthesize_recipe_from_prompt(prompt)
assert recipe is not None
self.assertTrue(recipe_has_playbook(recipe))
self.assertGreaterEqual(len(recipe["steps"]), 3)
self.assertEqual((recipe.get("source") or {}).get("compiled_from"), "prompt_text")
self.assertTrue((recipe.get("output") or {}).get("need_attachments"))
instr = build_scheduled_turn_instruction(
prompt_text=prompt,
mode="scheduled",
lang="en",
recipe=recipe,
previous_run={
"status": "success",
"finished_at": "2026-08-10T06:00:00+00:00",
"reply_text": "Found 12 candidates; 3 confirmed.",
},
)
self.assertIn("Scheduled playbook", instr)
self.assertIn("Previous run context", instr)
self.assertIn("Found 12 candidates", instr)
with_prev = append_previous_run_context(
"base",
previous_run={"status": "failed", "error": "timeout on CLI"},
lang="en",
)
self.assertIn("timeout on CLI", with_prev)
class ScheduleRecipeToolTests(unittest.TestCase):
def setUp(self) -> None:
@ -220,6 +260,29 @@ class ScheduleRecipeToolTests(unittest.TestCase):
self.assertEqual((recipe.get("source") or {}).get("template_id"), "ume_alarm_tally_daily")
self.assertIn("UME", str(job.get("prompt_text") or ""))
def test_create_auto_compiles_structured_prompt(self) -> None:
prompt = (
"Daily unmanaged dying-gasp correlation.\n\n"
"Step 1 — Query BN EMS communication failures.\n"
"Step 2 — Query remote dying gasp alarms.\n"
"Step 3 — Correlate and attach XLSX report.\n"
)
out = schedule_create_tool().handler(
{
"tenant_id": self.tenant_id,
"owner_user_id": self.user_id,
"name": "dying-gasp-daily",
"prompt_text": prompt,
"schedule_kind": "cron",
"schedule_expr": "0 14 * * *",
"lang": "en",
}
)
self.assertTrue(out.get("ok"), out)
recipe = (out.get("job") or {}).get("recipe") or {}
self.assertTrue(recipe_has_playbook(recipe))
self.assertGreaterEqual(len(recipe.get("steps") or []), 3)
def test_simple_reminder_still_works(self) -> None:
out = schedule_create_tool().handler(
{

View file

@ -0,0 +1,120 @@
from __future__ import annotations
import os
import tempfile
import unittest
from pathlib import Path
from unittest.mock import MagicMock
from runtime.scheduler.service import enqueue_scheduled_job_run
from svc.persistence.assistant_store import reset_assistant_store_singleton
from svc.persistence.sqlite_store import SqliteStore
class EnqueuePlaybookBackfillTests(unittest.TestCase):
def setUp(self) -> None:
self._tmp = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
self.db = Path(self._tmp.name) / "enqueue.sqlite"
os.environ["OPS_ASSISTANT_DB_PATH"] = str(self.db)
os.environ["AIA_ASSISTANT_DB_BACKEND"] = "sqlite"
reset_assistant_store_singleton()
self.store = SqliteStore(str(self.db))
t = self.store.create_tenant("Team")
self.tenant_id = str(t["id"])
user = self.store.create_user_account(
tenant_id=self.tenant_id,
username="administrator",
display_name="Admin",
role="owner",
password_hash="x",
is_active=True,
)
self.user_id = str(user["id"])
def tearDown(self) -> None:
reset_assistant_store_singleton()
self._tmp.cleanup()
def test_enqueue_synthesizes_recipe_and_injects_previous_run(self) -> None:
prompt = (
"Hourly dying-gasp check.\n\n"
"Step 1 — Query BN EMS failures.\n"
"Step 2 — Query remote dying gasp.\n"
"Step 3 — Correlate and report.\n"
)
job = self.store.scheduled_job_create(
tenant_id=self.tenant_id,
name="dying-gasp",
prompt_text=prompt,
schedule_kind="interval",
schedule_expr="3600",
timezone_name="UTC",
lang="en",
delivery={"channel": "admin_chat"},
recipe={},
created_by_user_id=self.user_id,
)
prev = self.store.scheduled_job_run_create(
job_id=job.id,
tenant_id=self.tenant_id,
status="queued",
)
self.store.scheduled_job_run_update(
run_id=prev.id,
tenant_id=self.tenant_id,
patch={
"status": "success",
"reply_text": "Prior: 4 unmanaged NEs.",
"finished_at": "2026-08-10T06:00:00+00:00",
},
)
captured: dict[str, object] = {}
def _capture_create(**kwargs: object) -> MagicMock:
captured.update(kwargs)
return MagicMock(id="task-1")
self.store.oclaw_task_create = _capture_create # type: ignore[method-assign]
from runtime.scheduler import service as sched_service
from runtime.scheduler.session_resolver import ResolvedSession
resolved = ResolvedSession(
session_id="sess-sched-1",
tenant_id=self.tenant_id,
user_id=self.user_id,
channel="admin_chat",
account_id="",
external_chat_id="",
external_user_id="",
is_group=False,
)
original_resolve = sched_service.resolve_scheduled_session
original_worker = sched_service.ensure_worker_started
try:
sched_service.resolve_scheduled_session = MagicMock(return_value=resolved)
sched_service.ensure_worker_started = MagicMock(return_value="worker-1")
out = enqueue_scheduled_job_run(self.store, job=job, mode="scheduled")
finally:
sched_service.resolve_scheduled_session = original_resolve
sched_service.ensure_worker_started = original_worker
self.assertTrue(out.get("ok"), out)
payload = captured.get("payload")
self.assertIsInstance(payload, dict)
assert isinstance(payload, dict)
self.assertTrue((payload.get("metadata") or {}).get("scheduled_playbook"))
text = str(payload.get("text") or "")
self.assertIn("Scheduled playbook", text)
self.assertIn("Previous run context", text)
self.assertIn("4 unmanaged", text)
refreshed = self.store.scheduled_job_get(job_id=job.id, tenant_id=self.tenant_id)
assert refreshed is not None
self.assertIn("Query BN EMS", refreshed.recipe_json)
self.assertIn("compiled_from", refreshed.recipe_json)
if __name__ == "__main__":
unittest.main()

View file

@ -69,6 +69,9 @@ class ResolveScheduledViewerUsernameTests(unittest.TestCase):
job.specialist = "generalist"
job.created_by_user_id = str(self.member["id"])
job.schedule_kind = "interval"
job.recipe_json = "{}"
job.source_session_id = ""
job.name = "stretch"
run = MagicMock()
run.id = "run-1"