feat(agents): per-agent inference — momentum, overdue-task, recent-patterns, focus-area (ADR-0014 step 7)
All four agents bumped to v1.1.0. momentum (#114): infers engagement_trend ('up'|'stable'|'down') by comparing done-rate in the last 7 days vs the prior 7 days. Agent surfaces the trend in its snippet ("trending up — build on the momentum"). overdue-task (#115): infers lateness_tolerance_days (0/1/2) from snooze rate. Agent now filters tasks against the tolerance so low-urgency users aren't nagged about tasks that are only hours overdue. recent-patterns (#116): infers window_days (7/14/30) from feedback event density — sparse users get a wider window so the snippet isn't always empty. focus-area (#113): no inferred params (project-level feedback linkage needed, tracked under #78). preferred_areas pref was declared but ignored; agent now honours it as a tiebreaker and mentions it in the snippet. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
from datetime import datetime, timezone
|
||||
from typing import ClassVar
|
||||
from .base import BaseAgent, AgentInput, AgentOutput
|
||||
from .manifest import AgentManifest
|
||||
|
||||
_SEVEN_DAYS_S = 7 * 86_400
|
||||
from .base import BaseAgent, AgentInput, AgentOutput
|
||||
from .inference.history import UserHistory
|
||||
from .manifest import AgentManifest, InferredParam
|
||||
|
||||
|
||||
def _infer_window_days(history: UserHistory) -> int:
|
||||
"""Infer the optimal lookback window from feedback event density.
|
||||
|
||||
More events per day → a shorter window captures the user's current state
|
||||
accurately. Sparse feedback → widen the window to gather signal.
|
||||
"""
|
||||
n = len(history.events)
|
||||
if n >= 14:
|
||||
return 7
|
||||
if n >= 7:
|
||||
return 14
|
||||
return 30
|
||||
|
||||
|
||||
MANIFEST = AgentManifest(
|
||||
id="recent-patterns",
|
||||
version="1.0.0",
|
||||
description="Surfaces the user's reaction pattern from the last 7 days of feedback.",
|
||||
version="1.1.0", # bumped: window_days InferredParam added (#116)
|
||||
description="Surfaces the user's reaction pattern from recent feedback.",
|
||||
pref_schema={
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
@@ -29,20 +44,32 @@ MANIFEST = AgentManifest(
|
||||
required_consents=["data:core", "agent:recent-patterns"],
|
||||
output_contract={"type": "snippet", "format": "free_text"},
|
||||
ttl_sec=86_400,
|
||||
inferred_params=[
|
||||
InferredParam(
|
||||
key="window_days",
|
||||
ttl_sec=86_400, # recompute daily alongside snippet
|
||||
cold_start_default=7,
|
||||
min_history=5,
|
||||
infer=_infer_window_days,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class RecentPatternsAgent(BaseAgent):
|
||||
"""Surfaces the user's reaction pattern from the last 7 days of feedback."""
|
||||
"""Surfaces the user's reaction pattern from recent feedback."""
|
||||
agent_id: ClassVar[str] = MANIFEST.id
|
||||
ttl_seconds: ClassVar[int] = MANIFEST.ttl_sec
|
||||
version: ClassVar[str] = MANIFEST.version
|
||||
|
||||
def compute(self, inp: AgentInput) -> AgentOutput:
|
||||
window_days = max(1, int(inp.agent_prefs.get("window_days", 7)))
|
||||
window_s = window_days * 86_400
|
||||
now_ts = inp.now.timestamp()
|
||||
|
||||
recent = [
|
||||
f for f in inp.feedback_history
|
||||
if self._age_s(f.get("created_at", ""), now_ts) <= _SEVEN_DAYS_S
|
||||
if self._age_s(f.get("created_at", ""), now_ts) <= window_s
|
||||
]
|
||||
|
||||
counts: Counter[str] = Counter(f.get("action") for f in recent)
|
||||
@@ -50,13 +77,13 @@ class RecentPatternsAgent(BaseAgent):
|
||||
dwell_ms = inp.profile.get("mean_dwell_ms_30d")
|
||||
|
||||
if total == 0:
|
||||
prompt = "No tip reactions recorded in the last 7 days."
|
||||
prompt = f"No tip reactions recorded in the last {window_days} days."
|
||||
else:
|
||||
done = counts.get("done", 0)
|
||||
dismissed = counts.get("dismiss", 0)
|
||||
snoozed = counts.get("snooze", 0)
|
||||
parts = [
|
||||
f"Last 7 days: {total} tip reaction{'s' if total != 1 else ''} — "
|
||||
f"Last {window_days} days: {total} tip reaction{'s' if total != 1 else ''} — "
|
||||
f"{done} completed, {dismissed} dismissed, {snoozed} snoozed."
|
||||
]
|
||||
if dwell_ms is not None:
|
||||
@@ -74,6 +101,7 @@ class RecentPatternsAgent(BaseAgent):
|
||||
prompt = " ".join(parts)
|
||||
|
||||
snapshot = {
|
||||
"window_days": window_days,
|
||||
"recent_total": total,
|
||||
"action_counts": dict(counts),
|
||||
"mean_dwell_ms_30d": dwell_ms,
|
||||
|
||||
Reference in New Issue
Block a user