Adds ml/agents/ — five specialised sub-agents (overdue_task, momentum, time_of_day, recent_patterns, focus_area) each producing a prompt snippet from user signals. A registry wires them up; the orchestrator prompt in ml/serving/prompts.py synthesises their outputs into one tip via LiteLLM. Also wires /api/agents route in the API and updates the Dockerfile to copy the full ml/ tree with PYTHONPATH=/app so agent imports resolve correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
43 lines
1.5 KiB
Python
43 lines
1.5 KiB
Python
from __future__ import annotations
|
|
from typing import ClassVar
|
|
from .base import BaseAgent, AgentInput, AgentOutput
|
|
|
|
|
|
class OverdueTaskAgent(BaseAgent):
|
|
"""Reports the user's overdue tasks by count and age."""
|
|
agent_id: ClassVar[str] = "overdue-task"
|
|
ttl_seconds: ClassVar[int] = 3600 # 1h — overdue status changes infrequently
|
|
version: ClassVar[str] = "1.0.0"
|
|
|
|
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)
|