Offload heavy work
The sandbox imports only zod and hono, with 128 MB and 20 s CPU per run. Run PDF parsing, browsers and big libraries in a service you operate.
Minion runs package code in a small sandbox: a fresh V8 isolate per run, with a 128 MB heap, 20 seconds of CPU and 90 seconds of wall clock. That's plenty for calling APIs, transforming JSON and writing to storage. It isn't enough for PDF parsing, headless browsers, image processing or large libraries. Some of those can't be imported at all.
When the work doesn't fit, keep the Minion package as a thin orchestrator and run the heavy part in a service you operate. Minion doesn't host containers or long-running processes for you.
What the sandbox can import
Sandboxed code can import:
minion:runtimeforminion,fetch,packageStorage()andsql()minion:webfor web platform globals (used by package apps)- Your organization's own saved packages, as
minion:@<org>/<package> - Two npm packages:
zodandhono(and their subpaths, such ashono/html)
Everything else is rejected when the code is bundled:
- Any other npm package, with the error "isn't on the sandbox allowlist"
- Node built-ins such as
fs,path,crypto,child_processor anything undernode: - Imports by URL
You can't add npm packages to a Minion package. minion.dependencies in package.json lists other Minion packages, not npm modules. So pdfjs-dist, unpdf, Playwright, sharp and native addons are out, whatever their size.
Size and resource limits
- An
executemodule can be up to 256 KB of source. The bundled script can be up to 2 MB. - A saved package can have up to 200 files, 256 KB per file and 2 MB in total.
- Each run gets 128 MB of heap and 20 seconds of synchronous CPU.
- Each run has 90 seconds of wall clock, including time spent waiting on
fetch. Package apps and sync webhooks get 25 seconds. - A run can return up to 100 KB of JSON (1 MB for package apps).
- Each outbound
fetchhas a 60 second timeout and can return up to 5 MB.
Vendoring a library's source into package files doesn't get around these. It counts toward the file limits, gets bundled on every run, and still has to fit in 128 MB and 20 seconds.
When to offload
Offload when:
- the library you need isn't
zodorhonoand you can't replace it with a few lines of your own code - a run hits the memory limit or the CPU limit
- the work takes longer than 90 seconds, even with fast upstreams
- you need a browser, native binaries, a GPU or a file system
Don't use this guide for typecheck failures, a missing export or a secret host that isn't approved yet. Those are different problems with their own errors.
The pattern
- Keep the Minion package. It owns the secrets, storage, jobs and the export your agent calls.
- Stop and show the owner a plan before creating any paid compute. They create and pay for the service: a Fly machine, a container on their cloud, or a box they already run. Don't sign up for a cloud account or start a machine from
execute. - The service exposes a narrow HTTPS API. One job per endpoint (extract text, render a page) is better than a general shell.
- Save the service's bearer token as a secret and approve the service's host for it. Ask the owner to do this on the Secrets page.
- The package calls the service with
fetchand the{{secret:name}}placeholder. It doesn't import the heavy library.
What stays in the package
- Collecting the input: an email attachment, a storage object, a webhook body
- Auth to the service, through a secret placeholder
- The
fetchcall, with a timeout and a size check - Writing the result to
packageStorage()orsql(), or sending a notification - The job, webhook or event subscription that triggers the work
export default async function extractInvoiceText(input: { objectKey: string }) {
const response = await fetch('https://extractor.example.com/extract', {
method: 'POST',
headers: {
authorization: 'Bearer {{secret:invoiceExtractorToken}}',
'content-type': 'application/json',
},
body: JSON.stringify({ objectKey: input.objectKey }),
signal: AbortSignal.timeout(30_000),
})
if (!response.ok) {
throw new Error(`Extractor returned ${response.status}.`)
}
const body = await response.text()
if (new TextEncoder().encode(body).byteLength > 90_000) {
throw new Error('Extractor response is too large to return.')
}
return JSON.parse(body) as { text: string }
} Replace the example origin with the owner's service. Minion fills in the secret only for hosts the owner approved, so approve the host before the first call. Keep the returned payload under the 100 KB result limit, or write it to storage and return a key.
Long jobs: hand off, then get a webhook
If the work takes longer than a run's 90 seconds, don't wait for it. Start it and let the service call you back:
- The package sends the job to the service and returns right away with a job id.
- The package declares a webhook in its
package.json, mints it withminion.webhooks.mint, and the owner gives the service that URL and signing secret. - When the service finishes, it POSTs the result to the webhook URL. Minion verifies the signature and runs your export with the body.
- That export stores the result or notifies someone.
See Webhooks for the declaration, signature checking and testing with webhooks.dispatchSynthetic.
If the service also speaks MCP, an owner can add it as a remote MCP server instead and you call it as minion.mcp['name'].tool(args). See Remote MCP servers .
What doesn't work
- Importing the heavy library in a package "just for one export." Every export that imports it pays the cost, and most can't be imported at all.
- Making the import dynamic. The bundler still resolves it and still rejects anything off the allowlist.
- Copying
node_modulesor generated data files into the package. They count toward the 2 MB package limit and the 128 MB heap. - Running the heavy step in ad hoc
executebecause it "worked once." It has the same limits as a package run.
Services you could run
These are examples, not Minion products. The owner picks one and pays for it:
- Fly Machines: a small always-on or scale-to-zero process
- A container on your cloud: Cloudflare Containers, Cloud Run, ECS or similar, called over HTTPS
- An existing box: a VPS, home server or CI runner with a locked-down API
Write the service as a small HTTP API. Cap request size and page count. Return structured text, not raw library objects. A home or LAN service can be reached through a tunnel; see Local MCP tunnels .