The agent no longer picks a winner — it summarises every cluster so the
orchestrator can decide what's relevant. Scoring by overdue count overlapped
with the overdue-task agent. preferred_areas (project-ID based, broken label
matching) removed entirely.
Output format: numbered list of areas with task titles included.
Snapshot: {cluster_count, clusters: [{label, task_count, tasks}]}.
Version bumped to 3.0.0; inferred_params cleared.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
66 lines
2.4 KiB
Python
66 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import ClassVar
|
|
|
|
from .base import BaseAgent, AgentInput, AgentOutput
|
|
from .clustering import cluster_tasks
|
|
from .manifest import AgentManifest
|
|
|
|
|
|
MANIFEST = AgentManifest(
|
|
id="focus-area",
|
|
version="3.0.0", # output all clusters as context; no scoring (#129)
|
|
description="Clusters the user's task list and summarises all areas for the orchestrator.",
|
|
pref_schema={"type": "object", "additionalProperties": False, "properties": {}},
|
|
context_schema=["todoist.tasks"],
|
|
required_consents=["data:core", "data:todoist"],
|
|
output_contract={"type": "snippet", "format": "free_text"},
|
|
ttl_sec=86_400,
|
|
inferred_params=[],
|
|
)
|
|
|
|
|
|
class FocusAreaAgent(BaseAgent):
|
|
"""Clusters tasks and outputs a full area summary for the orchestrator."""
|
|
agent_id: ClassVar[str] = MANIFEST.id
|
|
ttl_seconds: ClassVar[int] = MANIFEST.ttl_sec
|
|
version: ClassVar[str] = MANIFEST.version # 3.0.0
|
|
|
|
def compute(self, inp: AgentInput) -> AgentOutput:
|
|
if not inp.tasks:
|
|
return self._make_output(
|
|
inp,
|
|
"No tasks available to identify focus areas.",
|
|
{"cluster_count": 0},
|
|
)
|
|
|
|
clusters, new_enrichments = cluster_tasks(inp.tasks, enrichment_cache=inp.enrichment_cache)
|
|
|
|
if not clusters:
|
|
return self._make_output(
|
|
inp,
|
|
"No tasks available to identify focus areas.",
|
|
{"cluster_count": 0},
|
|
)
|
|
|
|
lines = [f"The user's tasks are grouped into {len(clusters)} area(s):"]
|
|
for i, cluster in enumerate(clusters, 1):
|
|
titles = [t.get("content", "").strip() for t in cluster.tasks if t.get("content")]
|
|
titles_str = "; ".join(f'"{t}"' for t in titles[:8])
|
|
if len(titles) > 8:
|
|
titles_str += f" (and {len(titles) - 8} more)"
|
|
lines.append(f"{i}. {cluster.label} — {cluster.task_count} task(s): {titles_str}")
|
|
|
|
lines.append("(Task titles may be in any language — always write the tip in English.)")
|
|
|
|
snapshot = {
|
|
"cluster_count": len(clusters),
|
|
"clusters": [
|
|
{"label": c.label, "task_count": c.task_count,
|
|
"tasks": [t.get("content", "") for t in c.tasks]}
|
|
for c in clusters
|
|
],
|
|
"_new_enrichments": new_enrichments,
|
|
}
|
|
return self._make_output(inp, "\n".join(lines), snapshot)
|