Use when the user asks for a composite / full divination reading — anything like: - "give me a total divination" - "full reading" - "do the works" - "I Ching + tarot + transits" - "everything you've got"
This is an orchestrator that runs three oracles and synthesizes them into one coherent reading, not three readings glued together.
context (optional, string) — what the reading is about. Passed through to all three oracles. Example: "about the project I'm launching this week", "about my creative life right now". When absent, all three oracles do open/general readings.subject (optional) — whose chart the transit snapshot is measured against (default: the user). Routed through astral-tenant-kb disambiguation if a non-self person is named.include (optional) — subset of [iching, tarot, transits]. Default: all three.This orchestrator invokes the three component skills as-is — it does not re-implement their logic. Load and follow each:
skill_view(name='iching-api-reading') — six-cast hexagram reading via iching-study app at :4017.skill_view(name='tarot-single-card') — single cryptographic card draw from alexTarot deck JSON.skill_view(name='astral-transit-snapshot') — exact-now transit→natal aspects + fast-mover layer.Each component skill keeps its own RNG/API discipline, time anchoring, and pitfall rules. The composite does not override them.
A composite divination image is built in parallel with the text synthesis. As soon as the three calculations (six casts resolved, card drawn, transits ranked) are done, fire-and-forget a background process that renders the PNG while you write the reading. The user receives both: the image as a MEDIA: attachment, the text as the message body.
Renderer: scripts/compose-divination.py (in this skill's directory). Reads a JSON input file, writes a PNG. Render time ~0.2s on a small composite, fully programmatic (Pillow + the astro-font TTF), no image generator. Alex's strong preference: build divination/astro composites programmatically — never with an image generator. Do not propose AI-generated drafts for this composite; if visual direction is unclear, iterate on the Pillow script.
Glyph key for transit rows: see references/astro-glyph-key.md.
Design rules locked in (do not regress these):
- Background #f7f3ec parchment, ink #1a1a1a, highlight #E85D2C (changing-line bars only).
- Composite canvas is tall portrait (1080×1720) so the tarot, hexagrams, and transit rows all breathe. The renderer resolves bare tarot filenames like m17.jpg against /home/avalon/apps/alexTarot/public/cards/ so the tarot card never silently disappears when the orchestrator passes card["img"]. If tarot.reversed is true, the card artwork itself is rotated 180° in the composite while the title remains readable.
- Hexagram bars stacked bottom-up (lines[0] = line 1 = bottom). Keep line thickness substantial (~22px); “make the hexagram thinner” means reduce the overall diagram width, not the stroke/bar height. Current diagram width is narrower than early drafts (~205px wide), with broken-line center gaps scaled down accordingly. Solid for yang (7/9), broken with center gap for yin (6/8). Changing lines (totals 6 or 9) drawn in highlight; relating hexagram in default ink only — its changes are already shown on the primary.
- Arrow → between primary and relating, large (~100pt), only rendered when changing lines exist.
- Hexagram label format: #NN · English · Chinese (e.g. #21 · Shih Ho · 噬嗑) — serif ~30pt, CJK ~34pt, English name kept short (no slash-suffixed alternative names).
- Transit rows: strict labeled 4-column grid (A: transiting planet+sign, B: aspect/transit-type glyph, C: subject/natal planet+sign, D: annotation). Symbol block A+B+C is centered on the canvas; annotation D floats to the right of C and does NOT participate in centering. Column labels sit above A/B/C in muted serif italic (~16pt), with enough reserved header space to avoid overlapping glyphs: transiting planet, aspect, and <subject_name>'s planet when subject_name is passed (fallback: natal planet).
- Planet glyphs full size; sign glyphs ~75% of planet size and drawn in muted gray (140,130,120) so signs sit politely without competing. In the transiting column, draw the gray sign on the outside/left and the black planet on the inside/right toward the aspect column. In the natal/subject column, keep black planet left and gray sign right. Aspect/transit-type glyph is centered in the middle column with per-aspect optical sizing: trine/conjunction/opposition stay large (~48pt), while square and sextile render smaller (~40pt) because those glyphs are visually heavy in the astro font. Renderer accepts either canonical names (trine, square, etc.) or the unicode symbols produced by transit code (△, □, ☌, ☍, ⚹) and maps them to the astro font glyphs.
- Lunar node labels from transit APIs may arrive as North Node (T) / South Node (T); normalize those to the node glyphs r / s before rendering so they never fall through to ?.
- Mars glyph (J) is rendered slightly smaller than normal planets (~44pt vs 48pt) — the astro font's Mars sits oversized in its viewbox and dominates other planets otherwise. Mars is drawn inside a full-size planet slot measured with the normal planet font, so resizing Mars must not affect aspect glyph sizing, row width, sign placement, or neighboring glyph alignment. There's a mars_font constant, a planet_font(ch) selector, and a draw_planet_slot() helper that centers Mars in the nominal slot. If new planet glyphs ever show similar viewbox quirks, extend that slot-based selector pattern — don't try to rescale everything globally.
- Glyphs drawn via draw_baseline() helper so per-glyph bbox bottoms align to a shared baseline (the astro font has inconsistent vertical metrics).
- Annotation column D: orb + applying/separating + · in ~Nh or · Nd ago time-to-exact, in muted gray sans ~13pt. The orchestrator computes eta_str from orb / daily_speed × 24h; the script just renders it.
Iteration history note: Each visual refinement Alex requested (bigger labels → bigger arrow → column-justified grid → centered symbol block → smaller gray signs → Mars special-case) compounded — never revert a prior fix when applying a new one. Read the current state of compose-divination.py before patching.
Execution pattern (inside the orchestrator's execute_code block):
import json, subprocess, tempfile, os, pathlib
# After the three oracle calculations have resolved:
composite_input = {
"tarot": {"image_path": "/home/avalon/apps/alexTarot/public/cards/" + card["img"],
"name": card["name"], "reversed": bool(rev)},
"iching": {
"primary": {"number": prim["number"], "displayName": prim["displayName"],
"chineseName": prim["chineseName"], "character": prim["character"]},
"relating": ({"number": rel["number"], "displayName": rel["displayName"],
"chineseName": rel["chineseName"], "character": rel["character"]}
if reading["changingLineNumbers"] else None),
"lines": [{"total": L["total"], "changing": L["changing"]} for L in reading["lines"]],
"changingLineNumbers": reading["changingLineNumbers"],
},
"transits": [
{"transit_planet": h["tb"], "transit_sign": <sign of transit>, "aspect": h["a"],
"natal_planet": h["nb"], "natal_sign": <sign of natal>,
"orb_str": f"{int(h['orb'])}°{int(round((h['orb']-int(h['orb']))*60)):02d}'",
"tag": h["tag"]}
for h in top_aspects[:5]
],
}
# Write input + spawn renderer in background — do not wait
tmp_in = tempfile.NamedTemporaryFile('w', suffix='.json', delete=False)
json.dump(composite_input, tmp_in); tmp_in.close()
out_png = f"/tmp/divination-composite-{os.getpid()}.png"
script = "/home/avalon/.hermes/skills/astrology/total-divination/scripts/compose-divination.py"
proc = subprocess.Popen(["python3", script, "--input", tmp_in.name, "--output", out_png])
# ... continue with text synthesis ...
# At the end, wait briefly for the renderer (it's ~0.2s):
proc.wait(timeout=5)
assert os.path.exists(out_png) and os.path.getsize(out_png) > 50_000
print("COMPOSITE:", out_png)
The composite path is included in the final reply as MEDIA:<out_png> — placed at the very top of the message (replacing the bare tarot card image — the composite already contains the card).
Skip rules:
- If the renderer fails for any reason, the reading still ships — just fall back to the bare tarot MEDIA: line. Never block the text on the image.
- If include excludes any oracle, omit its zone from the composite (script handles missing iching or transits gracefully — pass empty/null).
astral-tenant-kb).context verbatim if supplied.Inside one execute_code block, do:
- POST /api/divination/throw-line six times (with 1–3s gaps) + POST /api/divination/reading → I Ching result.
- secrets.choice on the alexTarot deck JSON + reversal roll → tarot result.
- GET /api/transits/current + load/compute natal chart + local aspect computation → transit result.
The randomized I Ching gaps are still required (ritual gap, not a perf concern). Tarot and transits don't need gaps. Print all three structured results, then compose the reply.
The reply is one composite reading, not three sections stapled together. Each oracle gets a compact "card" of its derivation, but the headline is a synthesis paragraph that triangulates all three — looking for resonance, tension, or commentary between them.
Examples of resonance to listen for:
- I Ching's primary hexagram theme echoed in the tarot card's archetype.
- Transit's headline aspect matching the moving-line inflection ("change pressure" in both).
- Tarot reversal mirroring a separating/applying transit's direction.
- All three pointing at the same life-area when a context was supplied.
Examples of tension worth surfacing: - I Ching says "stay low" while tarot pulls a fiery upright card → the synthesis names the tension instead of forcing a single message. - Transits show release/separating while I Ching shows building moving lines → time-layering: outer field releasing, inner field still gathering.
Never invent resonance that isn't there. If the three feel unrelated, say so — and lean on whichever oracle had the strongest signal (tightest transit orb / clearest moving lines / unambiguous tarot draw).
Strict order, mobile-readable:
Composite image at the very top. MEDIA:<composite_png_path> — absolute first line of the reply, nothing before it. The composite already contains the tarot card, both hexagrams (with changing lines highlighted + arrow to relating), and the transit glyph rows. If the composite renderer failed, fall back to MEDIA:/home/avalon/apps/alexTarot/public/cards/<img> (bare tarot card) — never ship without an image.
Header line (italic). Subject · local time (UTC) · context if supplied.
Alex · 2026-05-23 08:24 PDT (15:24 UTC) · context: "about launching this week"
🎴 Tarot · 🪙 I Ching · 🔭 Transit — one-line slate. A single compact slate line naming what was drawn, before any interpretation:
🎴 The Tower (reversed) · 🪙 ䷽ 62 Hsiao Kuo → ䷞ 31 Hsien · 🔭 headline: Uranus □ natal Moon (0°13′, applying)
✨ The Synthesis (the headline section). 4–8 sentences. This is the reading. Triangulate the three oracles into one coherent present-moment picture. If context was supplied, anchor the synthesis there. End with a bold one-to-three-sentence one-breath summary.
Component cards (below the synthesis — derivation). Three compact sub-sections, each shorter than its standalone version because the synthesis already carried the meaning:
🪙 I Ching — show the cast stack (line 6 top, line 1 bottom), the hexagram header glyph + number + name (English / Chinese / pinyin) + relating hexagram if changing lines exist, then a 2–4 sentence interpretation. Moving-line text(s) quoted for changing lines.
🎴 Tarot — bold card name header (with "— Reversed" if applicable), arcana · suit · number line, 2–3 sentence reading. Tie back to the context if any.
🔭 Transit — top 3 exact-now aspects as bullets (transit-first format with orb in degrees/minutes and applying/separating tag if confident), a 1–2 line fast-layer note (Moon/Mercury/Venus/Sun/Mars highlights + any 48h ingress), and a 2–3 sentence interpretation focused on the tightest aspect.
secrets.choice, never random. The transit snapshot must use real positions from /api/transits/current + a real natal chart. No mental shortcuts.MEDIA: line at the top of the reply.context everywhere or nowhere. If context is supplied, all three component readings reflect it. If absent, all three read openly — don't invent a focus for any of them.astral-tenant-kb before running the transit snapshot. I Ching and tarot are generally querent-agnostic, but if you're reading for someone else, say so in the header.iching-study :4017, alexTarot :4033, transit-list-demo).secrets, not random.astral-transit-snapshot table (snapshot orbs, not calendar orbs).