Each agent now exports a module-level MANIFEST declaring id, version, pref_schema, required_consents, ttl_sec, and silenced_in_contexts. The registry surfaces both the agent and its manifest, and rejects on mismatch so the two cannot drift. ml/serving exposes GET /agents/registry; services/api proxies it as GET /api/agents/registry with a 60s in-process cache so admin pageviews don't hammer upstream. Failures aren't cached. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
from __future__ import annotations
|
|
from typing import ClassVar
|
|
from .base import BaseAgent, AgentInput, AgentOutput
|
|
from .manifest import AgentManifest
|
|
|
|
|
|
MANIFEST = AgentManifest(
|
|
id="overdue-task",
|
|
version="1.0.0",
|
|
description="Reports the user's overdue tasks by count and age.",
|
|
pref_schema={
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"lateness_tolerance_days": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"default": 0,
|
|
"description": "Days past due before a task is considered overdue. 0 = the moment it's late.",
|
|
},
|
|
},
|
|
},
|
|
context_schema=["todoist.tasks"],
|
|
required_consents=["data:core", "data:todoist", "agent:overdue-task"],
|
|
output_contract={"type": "snippet", "format": "free_text"},
|
|
ttl_sec=3600,
|
|
silenced_in_contexts=["vacation"],
|
|
)
|
|
|
|
|
|
class OverdueTaskAgent(BaseAgent):
|
|
"""Reports the user's overdue tasks by count and age."""
|
|
agent_id: ClassVar[str] = MANIFEST.id
|
|
ttl_seconds: ClassVar[int] = MANIFEST.ttl_sec
|
|
version: ClassVar[str] = MANIFEST.version
|
|
|
|
def compute(self, inp: AgentInput) -> AgentOutput:
|
|
overdue = [t for t in inp.tasks if t.get("is_overdue")]
|
|
top = sorted(overdue, key=lambda t: -t.get("task_age_days", 0))[:3]
|
|
|
|
if not overdue:
|
|
prompt = "The user has no overdue tasks at this time."
|
|
elif len(overdue) == 1:
|
|
t = top[0]
|
|
age = round(t.get("task_age_days", 0))
|
|
prompt = (
|
|
f'The user has 1 overdue task: "{t["content"]}" '
|
|
f"({age} day{'s' if age != 1 else ''} overdue)."
|
|
)
|
|
else:
|
|
items = ", ".join(
|
|
f'"{t["content"]}" ({round(t.get("task_age_days", 0))}d)'
|
|
for t in top
|
|
)
|
|
prompt = (
|
|
f"The user has {len(overdue)} overdue tasks. "
|
|
f"Top {len(top)}: {items}."
|
|
)
|
|
|
|
snapshot = {
|
|
"overdue_count": len(overdue),
|
|
"top_overdue": [
|
|
{"content": t["content"], "task_age_days": t.get("task_age_days", 0)}
|
|
for t in top
|
|
],
|
|
}
|
|
return self._make_output(inp, prompt, snapshot)
|