feat(clustering): 1h TTL + skip recompute when tasks unchanged
focus-area now recomputes at most once per hour, and only if the task list actually changed since the last compute. - focus-area TTL: 43200s → 3600s; version bumped to 2.1.0 - computeAndStore hashes sorted task contents (MD5) and checks the stored _task_hash in the existing snapshot; skips the ml-serving call when the hash matches and the output isn't expired - ml-serving injects _task_hash into the snapshot so the next cycle can compare Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ def _infer_preferred_areas(history: UserHistory) -> list[str]:
|
||||
|
||||
MANIFEST = AgentManifest(
|
||||
id="focus-area",
|
||||
version="2.0.0", # semantic clustering via nomic-embed-text (#97, #113)
|
||||
version="2.1.0", # 1h TTL + task-change detection (#129)
|
||||
description="Identifies the most congested semantic focus area in the user's task list.",
|
||||
pref_schema={
|
||||
"type": "object",
|
||||
@@ -37,7 +37,7 @@ MANIFEST = AgentManifest(
|
||||
context_schema=["todoist.tasks"],
|
||||
required_consents=["data:core", "data:todoist"],
|
||||
output_contract={"type": "snippet", "format": "free_text"},
|
||||
ttl_sec=43_200,
|
||||
ttl_sec=3_600,
|
||||
inferred_params=[
|
||||
InferredParam(
|
||||
key="preferred_areas",
|
||||
@@ -54,7 +54,7 @@ class FocusAreaAgent(BaseAgent):
|
||||
"""Identifies the most congested semantic focus area in the user's task list."""
|
||||
agent_id: ClassVar[str] = MANIFEST.id
|
||||
ttl_seconds: ClassVar[int] = MANIFEST.ttl_sec
|
||||
version: ClassVar[str] = MANIFEST.version
|
||||
version: ClassVar[str] = MANIFEST.version # 2.1.0
|
||||
|
||||
def compute(self, inp: AgentInput) -> AgentOutput:
|
||||
preferred: list[str] = inp.agent_prefs.get("preferred_areas", [])
|
||||
|
||||
@@ -662,7 +662,7 @@ class TestFocusAreaPreferredAreas:
|
||||
|
||||
def test_version_bumped(self):
|
||||
from ml.agents.focus_area import MANIFEST as FA_MANIFEST
|
||||
assert FA_MANIFEST.version == "2.0.0"
|
||||
assert FA_MANIFEST.version == "2.1.0"
|
||||
|
||||
def test_snapshot_uses_cluster_keys(self):
|
||||
tasks = [self._task("T", "work")]
|
||||
|
||||
@@ -199,6 +199,9 @@ class AgentComputeRequest(BaseModel):
|
||||
# Pre-fetched enrichment cache: {content_hash -> description}. Avoids re-calling
|
||||
# LiteLLM for task titles already expanded in a prior compute cycle.
|
||||
enrichment_cache: dict[str, str] = {}
|
||||
# MD5 of sorted task contents; stored in snapshot so the next cycle can skip
|
||||
# recompute when the task list hasn't changed.
|
||||
task_hash: Optional[str] = None
|
||||
|
||||
|
||||
class AgentComputeResponse(BaseModel):
|
||||
@@ -327,6 +330,8 @@ async def compute_agent(agent_id: str, req: AgentComputeRequest) -> AgentCompute
|
||||
log.error("agent_compute_failed", agent_id=agent_id, user_id=req.user_id, error=str(exc))
|
||||
raise HTTPException(status_code=500, detail=f"Agent compute failed: {exc}")
|
||||
|
||||
if req.task_hash:
|
||||
output.signals_snapshot["_task_hash"] = req.task_hash
|
||||
new_enrichments: dict[str, str] = output.signals_snapshot.pop("_new_enrichments", {})
|
||||
|
||||
log.info("agent_computed", agent_id=agent_id, user_id=req.user_id, expires_at=output.expires_at)
|
||||
|
||||
Reference in New Issue
Block a user