Skip to main content

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

CI runs the suite on Python 3.10 through 3.13, every recipe’s 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/ and docs/recipes/, which are generated (from docstrings by gen_api_docs.py, from the recipe READMEs by gen_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 export line 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.json by 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.
Names a guide leaves to the reader (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.
1. Register the entry point. 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.
Skip it and 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.
It writes the recipe’s own page, its step index, 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:
Next: the conventions a recipe README follows are in recipes/README.md.

License

Apache-2.0
Last modified on September 20, 2026