Programs
Testing
Because a program touches the world only through effects, testing it means faking one function. The kernel-fidelity harness imports the real guest kernel host-side and stubs the single host call, so tests exercise the actual curated builtins, import allowlist, span machinery and use() loading — not plain CPython.
The harness
tests/kernelenv.py in the anybao repo exposes one entry point:
from tests.kernelenv import load_kernel
def test_remind_posts_to_chat():
sent = []
class FakeAny:
def chat_send(self, space, chat_id, body):
sent.append((space, chat_id, body))
return {"versionId": "v1", "changeId": "c1", "recordIds": ["m1"]}
app = load_kernel(any_client=FakeAny())
remind = app.use("remind@v1")
remind.main({"space": "bao", "chatId": "chat1", "text": "stand up"})
assert sent[0][2]["text"] == "⏰ Reminder: stand up"
assert sent[0][2]["agent"] == {"name": "bao", "done": True}
load_kernel(effect=None, any_client=None, llm_chat=None, programs_dir=None, module_source=None) returns a fresh kernel app module; app.use("name@vN") loads real sources from repos/_agent/programs with the runtime's own resolution order (<spec>.py, then <spec>/program.py).
| Parameter | What it fakes |
|---|---|
any_client |
use("any@v1") becomes a shim whose every method call crosses the boundary as test.any and dispatches to your object; exceptions become guest-catchable AnyError (with status/code when the fake sets them) |
llm_chat |
use("llm@v1").chat(...) crosses as test.llm and calls your function |
effect(name, payload) |
everything else — http.*, config.get, time.now, … |
programs_dir |
another repo's programs/ (the connectors repo tests its own) |
module_source(spec) |
consulted first; return source text or {"source", "marker"} — bump the marker to model an edited program and exercise the probe cache |
Alias-qualified specs (agent:any@v1) shim identically. span.begin/end are absorbed. An unknown effect raises loudly, so a test cannot silently pass over a call it never expected.
Why it matters. A stray
import contextlibthat the allowlist rejects fails in the test exactly as it would in the wasm guest. The only thing your test replaces is the boundary — the same seam replay uses.
Faking HTTP
Connectors are tested by recording live replies once and replaying them from fixtures. The shape is just the effect fake:
import json
from pathlib import Path
FIX = json.loads(Path("tests/fixtures/github_repo.json").read_text())
def fake_effect(name, payload):
if name == "http.get":
return {"status": 200, "headers": {}, "body": json.dumps(FIX)}
if name == "time.now":
return 1_700_000_000.0
raise AssertionError(f"unexpected effect {name}")
app = load_kernel(effect=fake_effect)
gh = app.use("github@v1")
assert gh.repo("anyproto", "any")["full_name"] == "anyproto/any"
Fixtures under tests/fixtures/*.jsonl are JSONL — one record per line is the parse contract; view them with jq . and never reformat the file.
One-shot runs
For an end-to-end check against a real server, run the program from disk with the local resolver, or the deployed copy with the space resolver:
anyrt run remind@v1 --args '{"space":"bao","chatId":"…","text":"hi"}'
anyrt run remind@v1 --from-space bao --args '{…}' # the deployed source
anyrt run mytool@v1 --secrets-file ./.connectors.env # seeds for this run only
Both print {status, value, error, traceRef, durationMs, fuelUsed} and write a trace. A running anyrt serve offers the same through its loopback control API — POST http://127.0.0.1:7010/run with {program, args?} for a deployed program or {source, args?, program?} for inline text (the source is served from the request body; its use() imports still resolve through the space).
Golden replay
A recorded trace is a test asset. In strict mode the runtime consumes the log as a cursor: the next effect call must match the next record's effect and key, cell and span records are checkpoints, and any drift is a divergence error naming the expected record. Record once, assert forever — no server, no keys. The mechanics and the loose mock variant are on Traces and replay.
Running the suite
nix develop # canonical env
uv sync
make kernel # componentized CPython → bin/kernel.wasm
make runtime # runtime/target/release/anyrt
uv run pytest # guest-module + wire tests; runtime e2e skips without kernel + binary
make test # kernel + cargo test + pytest
make lint # ruff + clippy -D warnings + fmt --check
Program tests need no kernel build — the harness runs the guest module host-side. The runtime e2e tests need make kernel and make runtime and skip otherwise.