Development¶
Working on Handler itself: where to start, running a checkout, releases, checking a host, and what
still needs a real host. The rules for changing the code (boundaries, contracts, conventions) are in
CLAUDE.md; where each block lives: code-map.md.
Start here¶
code-map.md: where each block lives, how the boundaries are enforced, which test suite answers which question.architecture.md: the design and why.model.md: every concept and its rules (the domain model).design/: the design of each feature, and what is not built yet.contracts.md: the normative contracts (sections A–H, cited in code and commits).packages/contracts(handler_contracts): pydantic models of every contract; JSON Schemas are generated from them intoschemas/.
From a checkout¶
uv sync
scripts/build-web-ui.sh # the web UI, into handler_control/web-ui/ (bun, or the toolbox image)
uv run handler setup # handlerd as a user service running this checkout, images built
./scripts/ci.sh # schemas up to date, ruff, ty, web apps, gateway, tests
uv run python -m handler_contracts.schemas # regenerate schemas/ after changing contracts
To use the checkout's handler everywhere: ln -s "$PWD/.venv/bin/handler" ~/.local/bin/handler; for a
pinned copy, uv tool install ./packages/cli.
Releases¶
scripts/build-wheel.sh # the wheel users install, into dist/
scripts/smoke-install.sh # that wheel installed by install.sh in a bare container, checked
scripts/build-docs-site.py # the documentation website, into docs-site/site
scripts/build-docs-site.py serve # the same, previewed on http://localhost:8000
The public repository holds one commit per release, exported from the maintainer's working
repository: its message is the version's CHANGELOG.md entry, its tag v<version>, and gitleaks
checks the tree first (test values that look like secrets: .gitleaksignore). Pull requests there
are closed with a note pointing to the issues (.github/workflows/pull-requests.yml,
CONTRIBUTING.md).
A tag v<version> (handlerd's __version__) runs .github/workflows/release.yml: the checks,
the wheel and its smoke test, then the upload to PyPI (Trusted Publishing: no token, only that
workflow in the public repository, once the maintainer approves it), then a GitHub release holding
the wheel and install.sh. A version is never uploaded by hand: PyPI takes each one once. Users
then install with uv tool install handler-ai (or pipx install handler-ai) and handler setup,
and update with uv tool upgrade handler-ai; install.sh is for people without uv: it installs
uv, then the same. The tool's commands: handler, handlerd, handler-harness,
handler-sandbox-podman, and handler-ai (handler under the package's name, for uvx
handler-ai). handler setup builds the toolbox (about 2.5 GB, once) then
every harness image on it. The user's toolbox is ~/.config/handler-ai/toolbox/ (Settings → Images →
toolbox → Edit its Containerfile), their addons go in ~/.config/handler-ai/addons/, their skills in
the Skills page: none of it needs this repository.
Harnesses and tools are not version-pinned: handlerd rebuilds their images weekly (Settings → Images
→ Automatic updates: weekly, daily or off, or Update now; also after a Handler upgrade), and each
new image is checked before agents switch to it: a broken release keeps the image they have.
handler image rebuild claude-code rebuilds one now.
The documentation website is README.md and docs/ built with Zensical
(zensical.toml: its navigation, the public repository's URL). A push to main of the
public repository publishes it on GitHub Pages (.github/workflows/docs.yml). A link to a file
that is not a page points at that file on GitHub; a link to a path that does not exist stops the
build, as scripts/ci.sh checks.
To validate on the host¶
What a feature does is in the user docs and its design note
(design/); what changed in a release, in CHANGELOG.md. What follows
is what the test suite cannot prove here (it runs with the fake sandbox and agent, as root in a
dev container, without real accounts) and still needs a run on a real host:
- The machine:
handler setup(orhandler-sandbox-podman check, contract A.8) says what it lacks. - Sandboxes:
uv run pytest tests/sandbox, as your user (keep-id, same-path mounts, proxy egress, confinement levels;HANDLER_TEST_OFFLINE=1skips the tests needing the internet). - Secrets through the network filter: a real turn of each harness with its token or key swapped in by the sandbox's proxy.
- Harness terminal (
handler terminal):uv run pytest tests/sandbox -k tty, thensystemctl --user restart handler-aiunder a runninghandler terminal. - Automations:
uv run pytest tests/automations/test_podman.py(toolbox built), thenhandler automation new env-issues --from env-issueson one of your providers andhandler automation try env-issues; an hourly automation running an agent with an interface on a provider, its result in the Inbox page and on a phone. The e2e test (test_an_agent_scheduled_from_its_settings) needs the Playwright image. - Addons: the Shopify addon against a real store (
tests/addons/test_shopify.py, opt-in). - Browser and mini apps: the lists at the end of
design/browser.mdanddesign/miniapps.md(real push services, iOS, Firefox, GNOME shortcuts, pi's MCP servers).
Known gaps¶
- The phone does not show project contexts yet.
- Not built yet, by feature: the last section of each design note (addons, browser, mini apps,
remote control) and the open part of
design/services.md.