12 KiB
WebAssembly SDK instructions
These instructions apply to work under sdk/ and supplement the repository-level AGENTS.md.
This file is maintainer guidance, not consumer documentation. Keep setup and the supported public surface in sdk/README.md. Keep exact API shapes in sdk/fx-sdk.js, its tests, and the declarations in sdk/types/. Add details here only when they help an agent choose the right owner, preserve a non-obvious invariant, or run the right proof. The native Node-API architecture and security model are documented in sdk/NAPI.md.
Start from the correct owner
The SDK has two WebAssembly surfaces and one shared JavaScript host layer:
| Concern | Source of truth |
|---|---|
| Shared loader, host imports, adapters, and public JavaScript exports | sdk/fx-sdk.js |
Headless ACP composition for fx-core.wasm |
src/wasm_core_main.zig |
Interactive terminal entry point for fx-term.wasm |
src/wasm_term_main.zig and runWasmTerminal in src/main.zig |
| Native and WebAssembly capability policy | src/core/hosts/runtime_profile.zig |
| Host-backed terminal session persistence | src/core/app/app_session_runtime.zig and sdk/fx-sdk.js |
Browser workspace contract and shell.run bridge |
src/core/hosts/js_host_workspace.zig and src/tools/shell/browser_shell.zig |
| Browser workspace AGENTS.md reads | src/core/hosts/js_host_workspace.zig, src/builtins/context.zig, and sdk/fx-sdk.js |
| Browser device login, OAuth session persistence, and URL opening | src/core/auth/js_host_auth.zig, src/core/auth/oauth_session.zig, and src/core/hosts/js_host_url_opener.zig |
| WASI target, optimization mode, threading, and artifact names | build.zig |
| Core browser fixture and its automation contract | sdk/index.html and sdk/tests/test-core-browser.mjs |
| Terminal fixture and static packaging contract | sdk/term-demo.html and sdk/scripts/package-term-demo.mjs |
| Durable sessions: log, leases, queue, deadlines, UI stream, and the harness contract | sdk/durable.js (no fx imports) |
| When a libfx steer interrupts, waits, joins, or hands off a turn | src/acp/libfx_steering.zig, used by src/acp/server.zig and src/acp/prompt.zig; the durable placement rules in sdk/durable.js |
| fx as a durable harness: engine options, rerun policy | sdk/fx-harness.js |
A durability over a World the app supplies; local() and vercel() are built on it |
sdk/durable/world.mjs |
| TypeScript declarations for every entry point | sdk/types/, checked by sdk/tests/test-types.mjs |
| Checkpoint format, including the libfx version, tool set, and model each checkpoint records | src/core/agent/runtime/checkpoint.zig |
| Images a session saves once, by name: naming in journal events and checkpoints, and the two-phase open | src/core/session/session_codec.zig, src/acp/libfx_journal.zig, and src/acp/server.zig |
A session's image store (the internal persistenceImages key) and storing before sending |
sdk/fx-sdk.js; a durable session's images are World streams in sdk/durable.js |
| Commands required by CI | .github/workflows/ci.yml |
Do not treat the demos or this file as the implementation contract. When prose and behavior disagree, establish the current behavior from code and focused tests before changing either.
Preserve these invariants
- Keep
sdk/fx-sdk.jsa dependency-free ECMAScript module. A bundler, framework, or runtime package must not become necessary to load the SDK. - Keep the core and terminal surfaces distinct.
fx-core.wasmstarts the headless ACP server;fx-term.wasmstarts the interactive terminal. Shared loader changes must be validated against both. - Detect JavaScript Promise Integration (JSPI) by capability through
supportsJspi(). Do not replace feature detection with browser or version sniffing. Keep loader errors, demo fallback states, and the compatibility statement insdk/README.mdconsistent. - Treat JavaScript host stores as durable contracts. Session and OAuth snapshots are opaque bytes with optimistic revisions. Preserve
FX_SESSION_REVISION_CONFLICTandFX_OAUTH_SESSION_REVISION_CONFLICT. Persist configuration only after fx accepts it, and do not collapse prompt-history outcomes into generic success. - Preserve cancellation and lifecycle behavior. Fetch cancellation must reach the host
AbortSignal; terminal subscriptions must be released exactly once;abort()must settleexitedand must not leave input or resize listeners attached. - The WebAssembly runtime is not the native runtime. Keep native tools disabled. The optional workspace host may expose only completion-only
shell.runthrough its typed boundary and permission policy. Its schema is exactly{ action: "run", command }; native profiles, TTYs, and managed running handles are unavailable. Any additional capability requires its own typed host boundary, permission review where applicable, and coverage on the affected surface. - The optional workspace
readFileboundary serves only project instruction loading. Keep it limited to absoluteAGENTS.mdpaths inside the workspace root or home, with UTF-8 validation, the 1 MiB copy limit, and the shared abort path. It is not a model-callable file tool. A workspace withoutreadFilemust report the omitted instructions rather than skip them silently. - Keep workspace version 1 constrained to an ephemeral, non-git workspace whose normalized
cwdequalsroot. Preserve command and output limits, the 30-second maximum deadline, and Ctrl+C cancellation through the shared host-effect abort path. window.__fxCoreTestanddocument.body.dataset.stateare test interfaces for the core debugger. If either changes intentionally, update the browser test in the same change.- The live demos may pass a locally stored credential into the WebAssembly environment. Never print, serialize into artifacts, or add test assertions containing that credential.
Match the proof to the change
| Change | Minimum focused proof |
|---|---|
| Public export, shared loader, WASI import, or JSPI gate | Build and test both surfaces |
| Public export, option, event, or result shape | Update sdk/types/ and run node sdk/tests/test-types.mjs after npm ci --prefix sdk/node |
createFxAgent(), ACP translation, core session persistence, streaming, or cancellation |
Core build, core Node tests, and the browser test when browser behavior is involved |
| Live Gateway request or model-catalog translation | Core tests plus the opt-in live smoke test when a credential is available |
| How a session saves images: naming, the image store, staging at open | Core and terminal builds, bun sdk/tests/test-journal.mjs native wasm and wasm native, test-checkpoint.mjs both ways, the Zig tests of checkpoint.zig and journal.zig, then tests/tla/sdk/check.sh and bun tests/tla/drivers/drive-images.mjs with --backend native and wasm, which walks the graph of ImageStore.tla; change the spec with the code. For durable sessions, also the durable sessions row |
| Terminal adapter, input encoding, resize, cleanup, config, or prompt history | Terminal build and the headless terminal suite |
| Terminal session persistence or browser device login | Terminal build plus sdk/tests/test-term-session-resume.mjs or sdk/tests/test-term-login.mjs |
| Browser workspace metadata, permissions, execution, limits, or cancellation | Terminal build plus sdk/node/test-term-workspace.mjs |
| Browser workspace AGENTS.md reads | Terminal build plus sdk/node/test-term-agents-md.mjs |
encodeXtermKeyEvent() or xtermAdapter() only |
sdk/node/test-xterm-adapter.mjs |
| Core debugger query behavior or automation state | sdk/tests/test-core-browser.mjs |
| Terminal demo asset references, integrity, or cache policy | Package into a fresh temporary directory and inspect the generated HTML, manifest, and headers |
| Durable sessions: leases, fencing, takeovers, tool reruns, the UI stream | sdk/tests/test-durable-core.mjs on memory, local, world, and queue (the core with a scripted harness; world is an app-built World passed to world(), queue one whose queue delivers as Vercel Queues does, with queueLeases), sdk/tests/test-durable.mjs on memory, local, and world, then tests/tla/sdk/check.sh and bun tests/tla/drivers/drive.mjs send, lookup, write, cutoff (deadline cut-offs up to their bound), leased (a World without liveness, where each worker renews a short lease), and queue (a World whose queue shows whether a worker lives, so leases are held without renewals), which walk the real code along the TLC state graphs of DurableSession.tla; change the spec with the code. For how a session object takes prompts in (images and their image writes, cancels, and closing the agent), also run bun tests/tla/drivers/drive-intake.mjs on memory and --durability world, which walks the graph of PromptIntake.tla. For agent names and routing, also bun tests/tla/drivers/drive-agents.mjs, which walks AgentRouting.tla's graph with each process in a worker thread of its own. For what a delivery may run, on whose agent and store, and input limits, also bun tests/tla/drivers/drive-tenants.mjs, which walks TenantIsolation.tla's graph with four tenants and a forger sharing one route. With a Vercel access token, also run both against Vercel's World: test-durable.mjs vercel native and drive.mjs --durability vercel, configured as sdk/tests/vercel-storage.mjs describes |
| Steering: interrupts, waits during tools, joins, hand-backs on cancel, image handoffs, steer ids | The libfx steering tests in src/acp/libfx_steering.zig, sdk/tests/test-agent-steering.mjs and sdk/tests/test-durable.mjs on native and wasm, then tests/tla/sdk/check.sh and bun tests/tla/drivers/drive-steer.mjs ideal and faults, with --backend wasm too, which walk the real code along the TLC state graphs of DurableSteer.tla; change the spec with the code |
| Supported public behavior or setup | Update sdk/README.md in the same change |
Use Node.js 24 for SDK tests. CI relies on its JSPI implementation behind --experimental-wasm-jspi.
Core validation from the repository root:
zig build -Dwasm-surface=core
node --experimental-wasm-jspi sdk/tests/test-core.mjs
node --experimental-wasm-jspi sdk/tests/test-core-cancel.mjs
node --experimental-wasm-jspi sdk/tests/test-core-home-unavailable.mjs
node sdk/tests/test-core-browser.mjs
Set CHROME_PATH for the browser test only when Chrome or Chromium is outside the paths searched by the script.
Terminal validation from the repository root:
zig build -Dwasm-surface=term
npm ci --prefix sdk/node
npm run --prefix sdk/node test:term
node --experimental-wasm-jspi sdk/tests/test-term-session-resume.mjs
node --experimental-wasm-jspi sdk/tests/test-term-login.mjs
For a live Gateway transport change, AI_GATEWAY_API_KEY must already be present in the environment before running the opt-in smoke test:
node --experimental-wasm-jspi sdk/tests/test-core-live.mjs
For behavior changes, also follow sdk/README.md to serve the repository and drive one real interaction in the affected browser demo. Automated tests do not replace this runtime exercise.
The terminal packaging script removes its output directory before writing. Always give it a newly created temporary directory:
sdk_package_dir="$(mktemp -d)"
node sdk/scripts/package-term-demo.mjs "$sdk_package_dir"
Do not commit zig-out/, sdk/dist/, or other generated artifacts. Run git diff --check before creating a checkpoint. The repository-level build, binary exercise, and CI ship gates still apply.
Code review rules
- Flag a public contract change that lacks a focused regression test. Require a README update only when supported public behavior or setup changed.
- Flag code that makes one surface silently depend on the other surface's composition or capability profile.
- Flag user-agent checks for JSPI, swallowed session or OAuth revision conflicts, premature config writes, lost prompt-history outcomes, or terminal listener leaks.
- Flag workspace adapters that bypass typed metadata validation, permission admission, resource limits, or the shared cancellation path.
- Flag deployment changes that give mutable HTML the same long-lived cache policy as content-addressed JavaScript or WebAssembly assets.