Files
oO/ml/agents/overdue_task.py
alvis 305eeae38b feat(agents): manifest plumbing + GET /agents/registry (ADR-0014 step 3)
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>
2026-05-05 10:55:54 +00:00

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)