feat(agentscope): add memory system and automation job support
- Add consumer_registry and pipeline_registry for runtime orchestration - Add Visibility schema for message filtering - Add PipelineSpec for agent pipeline configuration - Add automation job models and configuration - Remove memory_prompt.py (consolidated into memory system) - Update runtime components: context_loader, context_service, orchestrator, runner, tasks - Update toolkit: tool_config, tool_middleware, custom tools (calendar, user_lookup) - Add auth_helpers and calendar_domain utilities - Add system_agents.yaml configuration
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from core.automation.scheduler import (
|
||||
AutomationSchedulerService,
|
||||
_compute_next_run_at,
|
||||
)
|
||||
from models.automation_jobs import ScheduleType
|
||||
from schemas.automation.config import AutomationJobConfig
|
||||
from schemas.automation.scheduler import DueAutomationJob
|
||||
|
||||
|
||||
class _FakeRepository:
|
||||
def __init__(self, jobs: list[DueAutomationJob]) -> None:
|
||||
self.jobs = jobs
|
||||
self.marked: list[tuple[UUID, datetime, datetime]] = []
|
||||
self.commits = 0
|
||||
self.rollbacks = 0
|
||||
|
||||
async def list_due_jobs(
|
||||
self, *, now_utc: datetime, limit: int
|
||||
) -> list[DueAutomationJob]:
|
||||
del now_utc
|
||||
return self.jobs[:limit]
|
||||
|
||||
async def get_job_config(self, *, job_id: UUID) -> AutomationJobConfig:
|
||||
del job_id
|
||||
return AutomationJobConfig.model_validate(
|
||||
{
|
||||
"agent_type": "memory",
|
||||
"model_code": "qwen3.5-flash",
|
||||
"enabled_tools": ["calendar.read", "user.lookup"],
|
||||
"input_template": "auto input",
|
||||
"context": {
|
||||
"source": "latest_chat",
|
||||
"window_mode": "day",
|
||||
"window_count": 2,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
async def ensure_latest_chat_session(self, *, owner_id: UUID) -> UUID:
|
||||
return owner_id
|
||||
|
||||
async def mark_job_dispatched(
|
||||
self,
|
||||
*,
|
||||
job_id: UUID,
|
||||
next_run_at: datetime,
|
||||
last_run_at: datetime,
|
||||
) -> None:
|
||||
self.marked.append((job_id, next_run_at, last_run_at))
|
||||
|
||||
async def commit(self) -> None:
|
||||
self.commits += 1
|
||||
|
||||
async def rollback(self) -> None:
|
||||
self.rollbacks += 1
|
||||
|
||||
|
||||
class _FakeQueue:
|
||||
def __init__(self) -> None:
|
||||
self.commands: list[dict[str, object]] = []
|
||||
|
||||
async def enqueue(
|
||||
self,
|
||||
*,
|
||||
command: dict[str, object],
|
||||
dedup_key: str | None,
|
||||
) -> str:
|
||||
del dedup_key
|
||||
self.commands.append(command)
|
||||
return "task-1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scan_and_dispatch_enqueues_memory_run_command() -> None:
|
||||
now = datetime(2026, 3, 19, 12, 0, tzinfo=timezone.utc)
|
||||
owner_id = uuid4()
|
||||
job_id = uuid4()
|
||||
repo = _FakeRepository(
|
||||
jobs=[
|
||||
DueAutomationJob(
|
||||
id=job_id,
|
||||
owner_id=owner_id,
|
||||
schedule_type=ScheduleType.DAILY,
|
||||
timezone="UTC",
|
||||
next_run_at=now - timedelta(minutes=1),
|
||||
)
|
||||
]
|
||||
)
|
||||
queue = _FakeQueue()
|
||||
service = AutomationSchedulerService(repository=repo, queue=queue)
|
||||
|
||||
result = await service.scan_and_dispatch(now_utc=now, limit=10)
|
||||
|
||||
assert result.scanned == 1
|
||||
assert result.dispatched == 1
|
||||
assert len(queue.commands) == 1
|
||||
run_input = queue.commands[0]["run_input"]
|
||||
assert isinstance(run_input, dict)
|
||||
assert run_input["forwardedProps"] == {"agent_type": "memory"}
|
||||
assert queue.commands[0]["automation_job_id"] == str(job_id)
|
||||
assert repo.commits == 1
|
||||
|
||||
|
||||
def test_compute_next_run_at_daily() -> None:
|
||||
now = datetime(2026, 3, 19, 12, 0, tzinfo=timezone.utc)
|
||||
current = datetime(2026, 3, 19, 11, 0, tzinfo=timezone.utc)
|
||||
|
||||
computed = _compute_next_run_at(
|
||||
current_next_run_at=current,
|
||||
now_utc=now,
|
||||
schedule_type=ScheduleType.DAILY,
|
||||
)
|
||||
|
||||
assert computed == datetime(2026, 3, 20, 11, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def test_compute_next_run_at_weekly() -> None:
|
||||
now = datetime(2026, 3, 19, 12, 0, tzinfo=timezone.utc)
|
||||
current = datetime(2026, 3, 10, 11, 0, tzinfo=timezone.utc)
|
||||
|
||||
computed = _compute_next_run_at(
|
||||
current_next_run_at=current,
|
||||
now_utc=now,
|
||||
schedule_type=ScheduleType.WEEKLY,
|
||||
)
|
||||
|
||||
assert computed == datetime(2026, 3, 24, 11, 0, tzinfo=timezone.utc)
|
||||
Reference in New Issue
Block a user