# How it works A module is pure WebAssembly: it gets JSON and returns the calls it wants made. The host makes every call with the session it was given, and hands each result back as the next event. ## The pieces ```mermaid flowchart LR PDS[(publisher's PDS)] -- wasm.module record + blob --> Host Host -- event JSON --> Module[module.wasm] Module -- calls, state, result --> Host Host -- XRPC with the user's session --> UserPDS[(user's PDS, services)] ``` - **Module**: a `.wasm` with no imports, and its `module.json` (entries, allowed calls, session). - **Host**: runs modules and makes their calls. Three hosts share one protocol: the browser (`javascript/src`), Node (`javascript/src/server`) and Rust (`rust/host`, behind the `atlibs` CLI). - **Session**: who the calls act as. A module names its session record, and a signed-in run may only make the calls that record's scope allows. - **Records**: modules, their OAuth client records, source and compositions are atproto records, so anyone can publish them and anyone can fetch and check them. ## The step protocol The host calls an entry once per event, with the action's input, `did`, `now`, the state the entry returned last and the event. The entry answers with calls to make and new state, or a result. ```js run import { load } from "./rust/src/harness.mjs"; const { run } = await load(new URL("./target/wasm32-unknown-unknown/release/hello.wasm", `file://${process.cwd()}/`)); const input = { did: "did:plc:alice", now: "2026-11-09T00:00:00.000Z", collection: "xyz.statusphere.status" }; console.log(JSON.stringify(run("count", { ...input, state: null, event: { type: "start" } }))); console.log(JSON.stringify(run("count", { ...input, state: null, event: { type: "result", id: "list", nsid: "com.atproto.repo.listRecords", ok: true, output: { records: [{}, {}] } } }))); ``` ```output {"calls":[{"id":"list","nsid":"com.atproto.repo.listRecords","params":{"collection":"xyz.statusphere.status","limit":"100","repo":"did:plc:alice"},"service":"did:plc:alice"}]} {"done":true,"result":{"collection":"xyz.statusphere.status","records":2}} ``` Events in: - `start`: the first event. - `result {id}`: a call finished; `ok` and `output`, or `ok: false` and `error`. - `message {id}`, `closed {id}`: a subscription's messages, and its end. - `timer {id}`: a timer the module set. - `input {data}`: data the host feeds into a running module. - `callback {id, cap}`: another module invoked a capability this run passed it. Output fields: `calls`, `send` (write up a subscription), `cancel`, `timers`, `state`, `result`, `done`. The run ends when the module says `done`, or when nothing is pending. ## Who a run acts as | where | acts as | chosen by | |---|---|---| | a page | the signed-in user's OAuth session | `BrowserOAuthClient` ([Sign in from a page](browser/oauth.md)) | | a server (`atlibs serve`, Node) | each request's signed-in user, with their own OAuth session | `/oauth/login`, then the `atlibs_session` cookie or bearer ([Server sessions](server/sessions.md)) | | the CLI | an OAuth session `atlibs login` saved | `--as ` | | tests, an operator account | an app password | `--app-password ` with `--password-file` or `--password-env` | | none of the above | nobody: public reads only | | Nothing is read from the environment: `ATPROTO_*` variables mean nothing to atlibs. ## Where calls go | call | goes to | |---|---| | `{ nsid, params }` / `{ nsid, input }` | the session's own PDS | | `{ nsid, params, proxy: "did:web:svc#id" }` | the PDS, which proxies it (`atproto-proxy`); signed out, a query goes to that service directly, no token | | `{ nsid, input, service: "https://svc" }` | that service, with a service-auth token the host mints | | `{ nsid, params, service: "did:plc:..." }` | a public read of that account's PDS, no token (`did:web:svc#id`: of that service) | | `{ nsid, ..., subscribe: true }` | a WebSocket the host holds; messages arrive as events | | `com.fedfork.exec.*` | the host itself: attest, did:key, and transports (iroh, WebRTC) | Details: [Calls](modules/calls.md). ## Records | collection | what | |---|---| | `com.fedfork.exec.wasm.module` | a module: its wasm blob, entries, allowed calls, session, hooks | | `com.fedfork.exec.oauth.clientMetadata` | an OAuth client: the scope a module's session gets | | `com.fedfork.exec.source.file`, `.source.manifest` | the source a module was built from, so anyone can rebuild it | | `com.fedfork.exec.composition` | modules wired together | A module record names its session record and its source manifest by URI and CID. A **pin** (the wasm's sha256 plus the record's grant fields) is what a lockfile checks. See [Publish a module](modules/publish.md). Previous: [Docs](README.md) | Next: [Run modules in a page](browser/run-modules.md)