docs

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 contextlib that 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.