Skip to content
docs
UseBuildChangelog
Open Lumin

Examples

horary-desk

KP horary in its oldest, plainest shape: type a question, pick a number from 1 to 249, get a chart cast for the moment of judgment. No birth data, no set_birth_profile. The one feature that makes this more than a novelty toy is a gate that can honestly say the chart is not ready to be read.

Tool chain

get_horary_chart_v2 runs first. It reads Moon connectivity, whether the Moon has a real link (by conjunction, aspect, or sign/star/sub disposition) to the significators of the matter asked about. When it does not, KP doctrine says the query is not ripe for judgment. The app treats that as a first-class outcome rather than forcing an answer.

When the chart is ripe, get_horary_advanced runs next (number intuition and cross-validation against the horary chart itself, not a birth chart), followed by one category tool chosen from the question's text: get_medical_horary for a health question, get_career_horary for a career question, get_lost_or_missing for a lost item or person, get_arrival_timing for "when will X arrive". get_horary_serial runs last, checking whether this number has effectively been asked before, which is how KP horary practice polices someone reusing a favourite number until they get the answer they want.

Inputs

  • question, free text, the querent's own words
  • number, an integer 1 to 249, ideally chosen spontaneously rather than a favourite
  • place, free text, the place of judgment (not a birth place)
  • utcOffsetMinutes, the offset in effect at the place of judgment
  • The moment of asking is stamped server-side, not taken from the client, so the number and the moment cannot be chosen after the fact

Call it

typescript
// Two routes. The follow up trend is its own call, with its own allowlist.
const ALLOWED_TOOLS = [
  "get_horary_chart_v2",   // always, this is the verdict and the gate
  "get_horary_advanced",   // always, does the number confirm the question
  "get_medical_horary",    // then exactly one of these four, by question type
  "get_career_horary",
  "get_lost_or_missing",
  "get_arrival_timing",
] as const;

const TREND_ALLOWED_TOOLS = ["get_horary_serial"] as const;

const result = await runLumin({
  allowedTools: ALLOWED_TOOLS,
  system: buildSystemPrompt(),
  user: buildUserPrompt(input),
  maxTokens: 16000,
  effort: "xhigh",
  signal: req.signal,
});

const parsed = ensureShape(
  parseJsonBlock<HoraryResponse>(result.text),
  validateShape,
);

Seven tools are on the allowlist; a typical run calls four to six of them, since only one of the four category tools fires per question.

Response shape

json
{
  "place": { "label": "Colombo, Sri Lanka", "latitude": 6.9271, "longitude": 79.8612, "utcOffsetMinutes": 330 },
  "asked_at": "2027-05-01T09:14:00Z",
  "number": 74,
  "category": "career",
  "moon_connectivity": {
    "connected": true,
    "note": "Moon in the star of the 10th cusp sub lord, a direct link to the matter asked."
  },
  "refused": false,
  "refusal_reason": null,
  "verdict": {
    "answer": "yes, with a delay",
    "timing": "Within the current sub period, expect movement in 6 to 10 weeks.",
    "reasoning": "10th cuspal sub lord signifies 2, 6 and 10; retrograde reading applies as delay, not denial, since this is horary."
  },
  "serial_note": "First time this number has been asked in this session window.",
  "disclaimer": "A horary reading answers the question asked, at the moment asked. It is not a substitute for the querent's own judgment."
}

A refused chart carries the same shape with the verdict fields null:

json
{
  "refused": true,
  "refusal_reason": "The Moon shows no connection to the houses this question turns on. KP practice holds this chart is not ready to be read; ask again once you feel the question freshly, with a new number.",
  "verdict": null
}

Screens

  • A question box and a number picker (1 to 249), with a note against picking a birthday or a favourite
  • A place and offset field for the moment of judgment
  • A verdict card: answer, timing, reasoning, all traceable to a named tool
  • A refusal state, styled distinctly from a normal result, not as an error

The interesting engineering detail

Most generative products are built to always produce an answer, because a blank result reads as a bug. This one is built to sometimes refuse, on purpose, because that is what the source material says a horary chart does when the Moon carries no connection to the matter. The refusal is not an error state in the JSON, it is a normal, validated response shape with its own fields. Building the honest-refusal path as a first-class outcome, rather than papering over it with a forced verdict, is the whole point of the app.

Where to next