Skip to main content
Scope: AI-agent-agnostic playbook for building complete Stoffel applications. Use this as the orchestrating path, then switch to the narrower skills for implementation details. Dependency assumption: use portable public dependencies by default. A local checkout is a separate, nonportable framework-development workflow and is allowed only when explicitly requested.

Use when

Use this playbook when the task is larger than one .stfl snippet: an app idea needs a runnable Stoffel program, Rust host code, typed client IO, local MPC smoke tests, and a path toward network deployment.

Goal

Make an AI agent build the smallest complete app first, verify every layer, then expand. Do not let the agent stop at plausible code, skip local MPC, or treat execute_local() as deployment.

Milestone 0: establish portability

Complete this milestone before privacy design or implementation:
  1. Discover and confirm the project root from the current working directory and repository markers such as Stoffel.toml, Cargo.toml, or .git. Do not invent or require a machine-specific path such as /workspace/....
  2. Select Stoffel and related project dependencies from public, reproducible sources in this order: the current crates.io release, then the official GitHub repository pinned to a full immutable commit SHA when the needed change is not published.
  3. Reject floating branches and any default workflow that requires a local path, sibling checkout, or external filesystem checkout.
  4. Use a local Stoffel checkout only if the user explicitly requests framework development. Label that route nonportable, document it separately, and keep the app’s default build on public dependencies.
  5. If no suitable public dependency is available, stop and report the missing dependency and attempted public sources instead of substituting a local checkout.
Record the discovered root and selected public source in the task evidence. Keep this skill version-agnostic; concrete versions belong in installation docs and the app’s dependency manifest.

End-to-end sequence

  1. Establish the dependency and filesystem portability contract.
  2. Resolve the application trust architecture and participant runtime.
  3. Define the privacy and output boundary.
  4. Choose backend and topology.
  5. Implement the minimal StoffelLang program.
  6. Validate with the CLI.
  7. Run local MPC with representative inputs.
  8. Build participant-client SDK integration around the compiled program.
  9. Generate typed client IO bindings from the exact bytecode.
  10. Add app, privacy-boundary, and network smoke tests.
  11. Prepare deployment artifacts and control-plane/client/network config.
  12. Prove the app in a clean-room environment.
  13. Hand off with real command output and remaining production assumptions.

1. Trust architecture and privacy boundary

For client-owned private input, the input owner’s device or process is the Stoffel MPC client. It submits the input through the Stoffel client protocol directly to the separately deployed MPC service. An application backend may provide public session configuration and receive non-sensitive lifecycle receipts or explicitly authorized opened aggregates, but it must not receive, deserialize, log, queue, cache, analyze, or persist participant plaintext. A backend gateway that accepts plaintext is a distinct, weaker trust model. Do not introduce it implicitly. Name it, document who can see the raw value, and require explicit approval before implementing it. Write this before coding:
Rules:
  • Do not begin implementation while the plaintext location, submission process, forbidden components, persistence allowlist, or participant runtime support is unresolved.
  • Treat client, app, backend, gateway, and a local Tauri/Rust process as different roles. In a desktop app, the local Tauri/Rust process may be the participant client; a remote HTTP service is still an application backend.
  • Use HoneyBadgerMPC for ordinary private arithmetic over application values.
  • Use AVSS only when commitments, curve-compatible artifacts, or threshold-cryptography outputs are part of the app boundary.
  • Keep constants, thresholds, weights, encodings, and transcript bytes public unless they are genuinely private inputs.
  • Name concrete unauthorized visibility: who would otherwise see which raw input, trace, losing order, support signal, or log value.

2. Repository shape

For a complete app, prefer this shape:
Keep reusable MPC logic in .stfl modules. Validate private input shape in the participant client before submission. Keep public metadata validation, authorization, session lifecycle, non-sensitive receipts, and public result mapping in the application control plane. Do not define participant private fields in control-plane request or persistence schemas.

3. Build the smallest StoffelLang program

Start with one entrypoint and one representative output. Use typed secret annotations when the scalar shape is known:
Then validate:
If the app uses named clear inputs instead of ClientStore, use --input name=value. Do not combine named inputs and ClientStore inputs in one local run unless that exact CLI/runtime version has been verified to support the combination.

4. Rust SDK boundaries

Separate the participant client from the application control plane. The participant-owned Rust client should:
  • load pinned Stoffel bytecode, generated bindings, and public deployment config;
  • use its assigned client slot and identity material;
  • validate its owner’s private input shape locally;
  • submit directly to the separately deployed coordinator/MPC parties;
  • decode only outputs authorized for that client.
The application control plane may authenticate members, issue public session configuration and client-slot capabilities, record non-sensitive submission status, coordinate public lifecycle transitions, and persist explicitly authorized opened aggregates. It must not accept private input fields or call .with_client_input(...) with participant values. A trusted local smoke harness may load or compile the program, inject representative fixture inputs, spawn local MPC only for development testing, and decode outputs for program-semantic assertions. Local trusted-harness path:
This local harness sees every fixture input. It proves program semantics, not that a production application service is outside the plaintext path. Deployment-shaped path:
Treat the deployment-shaped path as participant-client configuration. The participant-owned process selects its own slot, connects to the separately deployed MPC service, and submits its input without using the application control plane as a plaintext proxy. Treat .execute_local().await? as local testing where several MPC nodes/processes are spawned locally on the developer machine.

5. Typed client IO bindings

After the first passing bytecode build:
Generate bindings from that exact .stflb using the project’s documented binding flow. In Rust, include the generated module and reference ProgramManifest when configuring typed clients. Rebuild bindings after any .stfl source or ClientStore shape change.

6. Verification ladder

Run the narrowest check at each layer:
For docs or skill changes, also run:
Do not report success unless at least one command exercised each changed layer. For multi-user applications, also verify:
  • control-plane request and persistence schemas contain no participant private fields or generic private payload blobs;
  • a unique plaintext canary is absent from application-service requests, logs, traces, databases, caches, queues, analytics, crash reports, and receipts;
  • production application-service code does not call .with_client_input(...), spawn local MPC nodes, or use .execute_local();
  • participant clients can submit to the MPC network after public session bootstrap without proxying the input through the control plane;
  • each output is delivered only to its authorized recipient.

Clean-room completion evidence

Before declaring the app complete, test it from a fresh temporary directory or clean environment that has no Stoffel source checkout, sibling repository, or undeclared path dependency available. Starting from only the delivered app files, documented prerequisites, and network access to the selected public source:
  1. install or resolve dependencies using the recorded public source;
  2. run cargo metadata --locked --format-version 1 and verify every external Stoffel package resolves from a registry or pinned official Git revision;
  3. run the verification ladder for every delivered layer; and
  4. capture the clean environment description, dependency-resolution output, commands, exit status, and relevant test or smoke output.
A warm build in the implementation worktree is not clean-room evidence. If the clean build reaches for an external local path, a floating branch, an undeclared file, or an unavailable public dependency, the milestone fails; fix it or stop and report the blocker.

7. Deployment handoff

Before handing the app to deployment work, produce:
  • dist/program.stflb built from the current source;
  • generated bindings from that bytecode;
  • parties, threshold, backend, curve, preprocessing sizes, and expected client slots;
  • coordinator address plan;
  • node mesh addresses and node RPC addresses;
  • client identity material requirements;
  • expected client/output slots;
  • exact local smoke command and output;
  • known production assumptions or SDK gaps.
Then switch to Stoffel Deployment Runbook.

Common pitfalls

  • Starting from Rust host code before the .stfl boundary is clear.
  • Treating a backend service, participant client, CLI, and Tauri Rust process as the same trust role.
  • Sending participant plaintext to an application endpoint in the default client-owned-input architecture; a gateway that does this is a separately approved degraded-trust design.
  • Using local fixture injection as evidence for the production private-input path.
  • Silently replacing unsupported browser/client functionality with a plaintext backend gateway.
  • Revealing intermediate secrets to make examples easier.
  • Treating local MPC as production deployment.
  • Compiling .stfl at runtime in production clients instead of loading pinned bytecode.
  • Forgetting to regenerate typed bindings after changing ClientStore shape.
  • Reporting generated code without running stoffel check, stoffel build, and a local MPC smoke.
  • Building only in a worktree that hides a required local or sibling Stoffel checkout.
  • Claiming completion without clean-room dependency resolution and execution evidence.

Next playbooks