from __future__ import annotations from typing import ClassVar from .base import BaseAgent, AgentInput, AgentOutput from .manifest import AgentManifest MANIFEST = AgentManifest( id="momentum", version="1.0.0", description="Characterises the user's recent engagement trend from profile features.", pref_schema={ "type": "object", "additionalProperties": False, "properties": { "low_engagement_threshold_pct": { "type": "integer", "minimum": 0, "maximum": 100, "default": 25, "description": "Completion rate below which momentum hints at low engagement.", }, }, }, context_schema=["profile.features"], required_consents=["data:core", "agent:momentum"], output_contract={"type": "snippet", "format": "free_text"}, ttl_sec=21_600, ) class MomentumAgent(BaseAgent): """Characterises the user's recent engagement trend from profile features.""" agent_id: ClassVar[str] = MANIFEST.id ttl_seconds: ClassVar[int] = MANIFEST.ttl_sec version: ClassVar[str] = MANIFEST.version def compute(self, inp: AgentInput) -> AgentOutput: completion = inp.profile.get("completion_rate_30d") dismiss = inp.profile.get("dismiss_rate_30d") volume = inp.profile.get("tip_volume_30d") parts: list[str] = [] if completion is not None: pct = round(completion * 100) if pct >= 50: parts.append(f"The user completes {pct}% of tips (strong engagement).") elif pct >= 25: parts.append(f"The user completes {pct}% of tips (moderate engagement).") else: parts.append( f"The user completes {pct}% of tips " f"(low engagement — prefer simple, immediately actionable tips)." ) else: parts.append("No completion-rate data yet (new user).") if dismiss is not None: dpct = round(dismiss * 100) if dpct >= 40: parts.append(f"Dismiss rate is high ({dpct}%) — avoid repetitive or irrelevant tips.") elif dpct <= 10: parts.append(f"Dismiss rate is low ({dpct}%).") if volume is not None and int(volume) < 5: parts.append("Very few tips served so far — this is an early-stage user.") prompt = " ".join(parts) if parts else "No engagement data available yet." snapshot = { "completion_rate_30d": completion, "dismiss_rate_30d": dismiss, "tip_volume_30d": volume, } return self._make_output(inp, prompt, snapshot)