"""TAROT agent — three-card draw (situation / action / outcome). Draws cards deterministically from a daily seed so the reading stays stable for the day (same cards whether the agent runs at 08:00 or 14:00). Card meanings are precomputed here and passed as a structured snippet to the orchestrator, which weaves them into a grounded, actionable tip. """ from __future__ import annotations import hashlib from typing import ClassVar from .base import BaseAgent, AgentInput, AgentOutput from .manifest import AgentManifest # --------------------------------------------------------------------------- # Card definitions — Major Arcana only (22 cards, indices 0–21) # Each entry: (name, upright_meaning, action_hint) # --------------------------------------------------------------------------- _CARDS: list[tuple[str, str, str]] = [ ("The Fool", "new beginnings, spontaneity, a leap of faith", "start something without overthinking"), ("The Magician", "skill, willpower, resourcefulness", "use what you already have"), ("The High Priestess","intuition, inner knowing, patience", "listen to what you already sense is true"), ("The Empress", "abundance, creativity, nurturing", "invest energy in something generative"), ("The Emperor", "structure, authority, discipline", "set a boundary or impose order"), ("The Hierophant", "tradition, guidance, shared values", "seek or offer mentorship"), ("The Lovers", "alignment, choice, commitment", "make a decision you have been avoiding"), ("The Chariot", "determination, focus, forward motion", "push through the resistance"), ("Strength", "inner courage, patience, gentle persistence", "stay the course with compassion"), ("The Hermit", "solitude, reflection, inner guidance", "step back and think before acting"), ("Wheel of Fortune", "cycles, turning points, inevitable change", "acknowledge what is shifting around you"), ("Justice", "fairness, truth, cause and effect", "audit a recent decision for its real consequences"), ("The Hanged Man", "pause, surrender, new perspective", "release your grip on the outcome"), ("Death", "endings, transformation, release", "let go of what no longer serves you"), ("Temperance", "balance, moderation, patience", "blend two competing demands"), ("The Devil", "attachment, habit, shadow patterns", "name a loop you are stuck in"), ("The Tower", "sudden disruption, revelation, necessary collapse", "accept the thing that already broke"), ("The Star", "hope, renewal, calm after the storm", "trust that recovery is already underway"), ("The Moon", "uncertainty, illusion, the unconscious", "sit with ambiguity rather than forcing clarity"), ("The Sun", "clarity, vitality, success", "act from your most energised self"), ("Judgement", "reflection, reckoning, a call to rise", "respond to a long-deferred summons"), ("The World", "completion, integration, a cycle closing", "acknowledge what you have finished"), ] _POSITIONS = ("situation", "action", "outcome") def _daily_draw(user_id: str, date_str: str) -> list[int]: """Return three distinct card indices seeded by (user_id, date).""" seed = hashlib.sha256(f"{user_id}:{date_str}".encode()).digest() indices: list[int] = [] offset = 0 while len(indices) < 3: val = int.from_bytes(seed[offset:offset + 2], "big") % len(_CARDS) if val not in indices: indices.append(val) offset = (offset + 2) % (len(seed) - 1) return indices MANIFEST = AgentManifest( id="tarot", version="1.0.0", description="Daily three-card draw (situation/action/outcome) that frames the tip as a symbolic reflection.", pref_schema={ "type": "object", "additionalProperties": False, "properties": { "enabled": { "type": "boolean", "default": True, "description": "Set false to disable the tarot agent for this user.", }, }, }, context_schema=[], required_consents=["data:core"], output_contract={"type": "snippet", "format": "free_text"}, ttl_sec=3_600 * 6, # stable for 6 h; refreshes mid-day at most twice silenced_in_contexts=[], inferred_params=[], ) class TarotAgent(BaseAgent): """Produces a three-card reading as a prompt snippet.""" agent_id: ClassVar[str] = MANIFEST.id ttl_seconds: ClassVar[int] = MANIFEST.ttl_sec version: ClassVar[str] = MANIFEST.version def compute(self, inp: AgentInput) -> AgentOutput: date_str = inp.now.strftime("%Y-%m-%d") indices = _daily_draw(inp.user_id, date_str) reading: list[dict] = [] parts: list[str] = [f"Today's tarot reading ({date_str}):"] for pos, idx in zip(_POSITIONS, indices): name, meaning, hint = _CARDS[idx] reading.append({"position": pos, "card": name, "meaning": meaning, "hint": hint}) parts.append(f" {pos.capitalize()} — {name}: {meaning}. Hint: {hint}.") parts.append( "Weave these symbolic themes lightly into the tip — " "ground them in practical, specific action. " "Do not explain the cards; let their meaning shape the advice." ) prompt = "\n".join(parts) snapshot = {"date": date_str, "reading": reading} return self._make_output(inp, prompt, snapshot)