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 wordsnumber, an integer 1 to 249, ideally chosen spontaneously rather than a favouriteplace, 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
// 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
{
"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:
{
"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
- Research and data use cases for the "ask the sky" web toy and reproducibility-study rows this pattern supports.
- Place-based and operations for the lost-property desk row.