a library, not a service.

there is no hosted api here, no account and no dashboard. you install a python package, you bring your own key, and every round is written to your own disk in a file that re-derives. what that costs is printed before anything is spent.

how do you install it?

python 3.11 or newer. two dependencies, both of them arithmetic; everything on the network path is standard library, because an http client is not worth a supply chain for one post.

git clone .../lulumelon.git ± the repository
python3 -m venv .venv && source .venv/bin/activate ±
pip install -e . ± puts `lulu` on the path
pip install -e ".[dev]" ± pytest, to run the suite
python3 -m pytest lulumelon/tests ± offline, spends no key

without the install the same commands run as python3 -m lulumelon.cli. building it as a package needs setuptools 77 or newer, which is not a preference: the licence field is the spdx string, and every backend before 77 reads that as a table and stops.

where does the key go?

into the os keychain, under the service lulumelon and the account name of the engine. one command, no questions asked, and it spends about a cent proving the key works and that a search actually comes back with pages.

lulu setup ± paste the key, or pipe it: pbpaste | lulu setup
lulu doctor ± find the key, test it, price the call
lulu doctor --offline ± everything except the call

where the keychain will not take it, a .env file beside you is written instead, created at 0600 rather than corrected to it afterwards. the environment variables it reads, in order, are ANTHROPIC_API_KEY then LULU_ANTHROPIC_API_KEY, and the same pair for perplexity. the key is never printed, never put in shell history and never written into the repository.

two engines exist in this build, claude and perplexity sonar. that call is billed, so it is written down: it lands in ./ledger under a name beginning diagnostic__, priced by lulu usage like any other spend and refused by everything that scores an answer.

what are the commands?

fifteen, and these are the six a first round goes through.

lulu draft --site example.com ± writes a question set from a site
lulu screened --draft FILE ± that round as a document, with a pdf
lulu rivals --snapshot ROUND ± who the answers named, uninvited
lulu collect --subject FILE ± asks each question k times
lulu report --snapshot ROUND ± one brand, with what it withholds
lulu verify ± re-derive every chain on disk

the other nine. plan prices a round before it exists, size says how many prompts and draws a target width needs, usageprices what was spent from the provider’s own figures, ablate and lift ask what one source was worth, publish writes the pages under measured questions, and setup, init and doctor handle the key.

every command prints the most it can spend before it spends anything, and the guard stops a round rather than discovering the overrun afterwards. a ceiling is priced from the call it guards, which means the size of the prompt about to be sent and the cap the request carries, not from a constant.

what is inside the library?

two layers and a wall between them, and the wall is the product. mirror is pure arithmetic, reaches no network and does not import the other side, so an interval can be recomputed from a file with no key. collect is the only part allowed to reach a provider and it computes nothing.

from lulumelon.mirror.intervals import wilson_interval ±
wilson_interval(0, 10) ± Interval(low=0.0, high=0.434)
wilson_interval(6, 6) ± Interval(low=0.61, high=1.0)

mirror holds intervals, variance, stability, compare, sources, ablation, lift, report, screen, names and the types. collect holds ask, ledger, session, budget, detect, subject, audit, harvest, propose, replica and replay. 986 tests run over both with every socket closed.

what does a round look like on disk?

one file per round, append only, one json object per line, each line carrying the hash of the line before it. the round closes with a record saying how many calls it made and how they came out, hashed into the same chain, which is what makes a short file readable as short: a file somebody cut lines off the end of has lost the only sentence that said how long it was.

the name of the file is the round: subject, engine, surface, timestamp and sequence. the surface is the arm, api when the model could search while answering and api_unsearched when it could not, and it is derived from the request rather than accepted from a caller so two arms cannot be filed under one name.

a failed call is written down with the provider’s own reason and never retried. asking again until the answer is good is a filter over model output, which is the thing this exists to refuse.

what does it refuse to tell you?

a ranking the sample cannot support, a comparison across a model version change, a rate computed from a round that does not re-derive, and a question set screened on your own name. each refusal is printed where the number would have been, with the reason.

the licence is mit and the whole of it is on github. the key page in the repository goes further into what a call costs, with the figures read from the provider’s own documentation and the date they were read on.