Package layout
The public surface is the front door:import whileai as wai, under thirty
names (style, rule 1). whileai.simulations is the legacy
path and gains no new names. The folders below are the internals it is built
from; they are grouped by stage and may move between releases.
Source: github.com/whilehq/whileai-sdk/tree/main/whileai/simulations.
Development
smoke.sh, ruff, mypy, ty, line coverage, a plain-pip install of the built wheel into a clean venv (the check that catches a uv-only source pin), and a version-scheme check.
The code in the docs runs
uv run python scripts/check_doc_snippets.py executes every ```python block under docs/ against the installed package and CI runs it on every PR (the job the code in the docs runs). A page is one program: its blocks run in order, in one namespace, in a scratch directory, with no keys set and anything credential-shaped stripped from the environment. Where a page quotes a block’s output in the fence right after it, the quoted text has to match what the block printed; a block written as a session (>>> call followed by what it printed or raised) is checked line by line, with an exception compared as ValueError: message. Each block gets 180 seconds; a failure names the page and the line of the opening fence.
Three things are not run:
docs/api/anddocs/recipes/, which are generated (from docstrings bygen_api_docs.py, from the recipe READMEs bygen_recipe_docs.py). Recipe blocks assume a clone and are covered by the recipe smoke job, so fix a recipe’s README rather than its page.- A block that needs a key, when a sentence or
exportline above it on the page names the variable (WHILEAI_API_KEY,OPENAI_API_KEY,ANTHROPIC_API_KEY,MODAL_TOKEN_ID,TYPESAFE_API_KEY). A block that reaches for a key the page never mentions fails. - A block that is a sketch rather than a program (a signature, the shape of a return value, a live training run), listed in
scripts/doc_snippets/skips.jsonby page and a substring of its code, with a reason. The list sits off the page because Mintlify renders a fence info string as a filename badge and an HTML comment breaks its MDX parser.
agent, TOOLS, POLICY, scored) come from a fixture under scripts/doc_snippets/ that mirrors the page’s path below docs/ and runs before its first block; most fixtures are one line, from _common import *. Check one page with --page docs/evals.md -v, or a released wheel with --python /path/to/venv/bin/python.
Contributing a recipe
A recipe directory is necessary and not sufficient. Two steps past the directory are what CI checks, and both of them are invisible locally until a check goes red, so do them before the first push. CONTRIBUTING.md has the conventions; this is the wiring.tests/recipes/test_offline_examples.py
holds two collections: CLI_EXAMPLES, the scripts that answer --help
offline, and NEEDS_MODAL, the ones that need a Modal token. Every recipe
directory has to appear in one of them.
test_every_example_module_compiles fails with
new recipe directory with no CLI entry point in this test.
2. Regenerate the recipe pages. docs/recipes/** is generated from each
recipe’s README.md, so a new directory leaves the tree stale until you run
the generator and commit what it wrote.
docs/recipes/index.mdx and
the Recipes nav group in docs/docs.json. Skip it and the job
docs/recipes matches the recipe READMEs fails and names every stale file.
Then run the offline path and the linters the recipe jobs run: