Literals already:
Choice over exactly those values, so whatever
reaches the function is a value the function accepts. You leave the functions alone. What
you add is a spec that says in plain words what each argument means. By the end you have a
Dispatcher you can point at your own functions.
Setup
TYPESAFE_API_KEY. Two modules sit beside this file. trader.py holds the ten
functions, plus a TypeSafe client that reads answers from a cache, so re-rendering replays
the numbers below without calling the API. dispatch.py holds the code that reads a
signature and a spec and makes the call.
Find the closed sets in the signatures
The type hints already say which arguments come from a fixed list, and what is in each list.closed_sets reads a signature and sorts those arguments into three shapes: a
choice (a Literal, so one value out of the list), a set (a list[Literal[...]],
so any number of them), or a flag (a bool, so on or off). All ten functions are
defined in trader.py.
top_movers shows what gets left out. Of its three arguments, two are closed sets. The
third, limit, is an int, so it never gets a question and keeps its default of 3. Free
text, numbers and dates work the same way: no question, and the function’s default stands.
Write the spec
TheLiteral gives you the strings "1mo" and "3mo". It does not say that a user typing
“this quarter” means the second one. The spec says that. It holds a question per argument,
a line per option, a description per function, and one more question that picks between the
functions. It lives in spec.json, and an LLM can write it for you from the signatures.
stated makes an argument optional. It is a second yes/no question
asking whether the command says anything about that argument at all. When the answer is no,
the call leaves that argument out and the function’s own default applies.
A set argument gets its question once per member, with {} standing in for the member
name. "Does the user want {} in the comparison?" becomes one question per ticker.
Write each question about the idea rather than the words a user might pick, because the
match is on meaning: “is amd tracking nvidia lately” reaches rolling_correlation even
though neither tracking nor lately appears anywhere in spec.json. Avoid naming a
question after its parameter - "Which resolution?" gives the command nothing to match
against.
Turn the spec into questions
Dispatcher builds the questions from the spec once. Each command is then one request
carrying the choice of function and every function’s arguments, and the dispatcher reads
only the chosen function’s answers.
Run fourteen commands
Each line below is one request.confidence is the least certain judgement behind that
call.
symbol and
benchmark, draw from the same six tickers, and each ticker landed in the right argument
because the questions spell out the roles: the one being measured, named first against
the second one named, the yardstick. “compare nvda amd and msft over the past three
months” put three tickers in the set and left the other three out.
Running three of them:



Read the confidence
confidence reports the least certain judgement in the call, rather than the product of
all of them, since one wrong argument is enough to spoil the result. A product answers a
different question - “is every part right” - and it falls as a function takes more
arguments, whether or not any one judgement is shaky.
Where that number came from, argument by argument:
window and resolution are both omitted here, because “lately” does not say how far back
or on what bars, so rolling_correlation runs on its own defaults of one month and hourly
bars. That is what the stated question is for. Without it, the choice would have to name
some window, and it would have named one confidently.
Open it in the playground
The link below holds one command and the questions for the function it picked: the choice over the ten function descriptions, androlling_correlation’s four arguments. Edit the
command there and the arguments change with it.

