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:
qzl
2026-03-19 18:42:35 +08:00
parent 0661016827
commit 0abf51e837
55 changed files with 2172 additions and 1233 deletions
@@ -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)