# Freestyle Sandbox execution - Package: @bijectionhq/freestyle - Category: Sandboxes - Status: Preview - License: MIT - Bijection source (repository access required): https://github.com/bijectionhq/freestyle/tree/635839f04d41b7093b3dc13d30f94d773ff070d3 - Upstream: https://github.com/freestyle-sh/convex/tree/9977e7d052d8127db89832b02c7d541caa2b584f - Documentation: https://docs.bijection.com/components/using - Directory: https://bijection.com/components/freestyle ## Get the source Bijection repository access is required. Request access at https://bijection.com/contact. ```sh git clone https://github.com/bijectionhq/freestyle.git cd freestyle git checkout 635839f04d41b7093b3dc13d30f94d773ff070d3 ``` ## Before using this component Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute. ## Upstream package documentation Recorded 2026-10-23. This describes @freestyle-sh/convex; check the Bijection adaptation separately. Freestyle for Convex manages long-lived Freestyle VMs from Convex actions while keeping a reactive, cached record of each VM in your Convex database. It provides idempotent VM creation with retry recovery, tenant isolation via an app-supplied ownerId, and helpers for the full VM lifecycle including create, start, pause, exec, snapshot, resize, and delete. Read operations like get and list work in reactive Convex queries; mutating operations run in actions against the Freestyle API. ### Use cases - Building AI agent workspaces where each user or session needs an isolated full Linux environment with Docker and networking - Running untrusted or long-lived code execution sandboxes that need to be paused and resumed between user sessions - Snapshotting a configured VM state to use as a base image for future agent instances - Managing per-tenant VMs in a multi-tenant app where VM state needs to be reactively queryable alongside other Convex data - Executing shell commands inside a VM from a Convex action and returning structured results to the client ### How it works Register the component in `convex/convex.config.ts` using `app.use(freestyle)` and set your `FREESTYLE_API_KEY` environment variable on the Convex deployment. Instantiate a `Freestyle` client in your app's `convex/` directory by passing `components.freestyle`, then derive `ownerId` from server-side auth state rather than accepting it as a client argument to enforce tenant isolation. Mutating operations such as `create`, `start`, `pause`, `exec`, `resize`, `snapshot`, and `delete` make external HTTP requests to the Freestyle API and must run inside Convex actions. The `get` and `list` methods read from cached Convex records and can be used in reactive queries. Call `refresh` from an action when you need the current remote state synced back into Convex. VM creation is idempotent and retry-safe. The component writes a Convex record first, then provisions the remote VM with a random ownership marker stored in Freestyle metadata under the reserved `convex_component_id` key. If an action retries after the remote VM exists but before the Convex record was linked, the client locates the VM by slug and verifies the marker before linking, rejecting any VM with a mismatched marker. Firewall rules are required on creation because Freestyle uses a default-deny network model. Use an empty rules array for a fully isolated VM or declare only the traffic your workload needs. Multiple `Freestyle` client instances can be configured with different API keys to support multiple Freestyle accounts from a single Convex deployment. ### Upstream README # Freestyle for Convex Manage long-lived [Freestyle VMs](https://www.freestyle.sh/docs/vms) from Convex actions while keeping a reactive record of each VM in Convex. Freestyle VMs are the most powerful VMs for AI agents. They are full Linux machines with root access, Docker, systemd, nested virtualization, FUSE, eBPF, and full Linux networking. VMs start quickly, preserve memory when paused, and can be snapshotted or branched from an exact machine state. The component provides: - idempotent VM creation with retry recovery; - cached VM lifecycle and resource state for reactive queries; - create, refresh, start, pause, resize, update, exec, snapshot, and delete helpers; - explicit tenant isolation through an app-supplied `ownerId`; and - protection against adopting an unrelated Freestyle VM with the same slug. ## Install ```sh npm install @freestyle-sh/convex ``` Register the component in `convex/convex.config.ts`: ```ts import { defineApp } from "convex/server"; import freestyle from "@freestyle-sh/convex/convex.config.js"; const app = defineApp(); app.use(freestyle); export default app; ``` Set a Freestyle account API key on the Convex deployment: ```sh npx convex env set FREESTYLE_API_KEY your-api-key ``` Create an API key from the [Freestyle dashboard](https://dash.freestyle.sh) or with the CLI: ```sh npx freestyle@latest login npx freestyle@latest tokens create convex ``` ## Create an authenticated API Instantiate the component client in your app's `convex/` directory. Convex components cannot authenticate your app's users, so the wrapper must derive `ownerId` from trusted auth state. Do not accept `ownerId` as a client argument. ```ts import { v } from "convex/values"; import { Freestyle } from "@freestyle-sh/convex"; import { components } from "./_generated/api.js"; import { action, query } from "./_generated/server.js"; const freestyle = new Freestyle(components.freestyle); async function ownerId(ctx: { auth: { getUserIdentity(): Promise<{ subject: string } | null> }; }) { const identity = await ctx.auth.getUserIdentity(); if (!identity) throw new Error("Unauthenticated"); return identity.subject; } export const createWorkspace = action({ args: { slug: v.string() }, handler: async (ctx, { slug }) => { return await freestyle.create(ctx, { ownerId: await ownerId(ctx), slug, snapshotId: "freestyle/ubuntu", idleTimeoutSeconds: 600, firewall: { rules: [{ action: "allow", source: {}, destination: { public: true } }], }, }); }, }); export const listWorkspaces = query({ args: {}, handler: async (ctx) => { return await freestyle.list(ctx, { ownerId: await ownerId(ctx) }); }, }); ``` Freestyle has a default-deny network model, so `firewall` is required. Use `{ rules: [] }` for an isolated VM, or declare only the traffic the workload needs. See the [Freestyle quickstart](https://www.freestyle.sh/docs/quickstart) for firewall examples. ## Lifecycle and commands Mutating operations make external requests and must run in Convex actions: ```ts await freestyle.start(ctx, { ownerId, slug }); await freestyle.pause(ctx, { ownerId, slug }); const result = await freestyle.exec(ctx, { ownerId, slug, options: { command: "git status --short", timeoutMs: 300_000 }, }); await freestyle.resize(ctx, { ownerId, slug, options: { cpu: 8, memory: 16 * 1024, storage: 80 * 1024 }, }); const snapshot = await freestyle.snapshot(ctx, { ownerId, slug, options: { slug: `${slug}-configured` }, }); await freestyle.delete(ctx, { ownerId, slug }); ``` `get` and `list` read cached records and can run in reactive Convex queries. Call `refresh` from an action when the application needs the current remote state: ```ts const cached = await freestyle.get(ctx, { ownerId, slug }); const current = await freestyle.refresh(ctx, { ownerId, slug }); ``` ## Creation and retries `create` requires a stable Freestyle `slug`. The component reserves a Convex record first and adds a random ownership marker to the VM's Freestyle metadata. If the action is retried after the remote VM was created but before the Convex record was linked, the client finds the VM by slug and verifies that marker before linking it. A VM with the same slug and a different marker is rejected. The component reserves the `convex_component_id` metadata key and one of Freestyle's 64 VM metadata entries. Freestyle slugs are account-wide. Two owners may use the same logical name in Convex, but their remote VM slugs still need to be unique within the Freestyle account. Prefix or hash tenant identifiers when generating slugs for a multi-tenant app. ## Configuration The constructor reads `FREESTYLE_API_KEY` from the consuming Convex app by default. Explicit options support multiple Freestyle accounts and local API testing: ```ts const freestyle = new Freestyle(components.freestyle, { apiKey: process.env.SECONDARY_FREESTYLE_API_KEY, baseUrl: "https://api.freestyle.sh", }); ``` Each separately registered Convex component instance has its own tables. Each `Freestyle` client may also use a different API key. ## Development ```sh npm install CONVEX_AGENT_MODE=anonymous npx convex init npm run build:codegen npx convex codegen --typecheck disable npm test npm run typecheck npm run lint npm run format:check ``` The full backend-only example is in [`example/convex`](./example/convex).