Draws 3 Major Arcana cards from a daily seed (user_id + date) so the reading is stable within a day and unique per user. Card meanings and action hints are precomputed in the agent; the orchestrator receives a structured prompt snippet and is instructed to weave the themes into a grounded, practical tip without explaining the cards. No inferred params, no external data — requires only data:core consent. TTL 6 h (refreshes at most twice daily). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
5.8 KiB
Python
111 lines
5.8 KiB
Python
"""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)
|