Agent SwarmAgent Swarm
Guides

Scripts runtime

What the swarm-scripts runtime exposes to user code, what it does NOT expose, and how the typecheck stays aligned.

Swarm scripts are TypeScript modules persisted in the catalog via /api/scripts/upsert and executed by the scripts-runtime. The save-time typecheck and the runtime are deliberately aligned — what passes the typecheck is what actually runs. This page documents the full surface so script authors don't have to bisect.

What the runtime provides

Scripts run inside a bun run subprocess (src/scripts-runtime/eval-harness.ts) with the environment stripped to a small allowlist. Everything below is available as a plain global in user code AND typechecks cleanly.

ES2022 standard library

The typecheck loads lib.es2022.d.ts. All standard ES2022 built-ins resolve:

  • Primitives + boxed types: Number, String, Boolean, Symbol, BigInt
  • Collections: Array, Map, Set, WeakMap, WeakSet
  • Errors: Error, TypeError, RangeError, SyntaxError, ReferenceError
  • Async: Promise, Promise.all, Promise.allSettled, Promise.race, Promise.any
  • Iteration: Iterator, IterableIterator, Generator
  • Reflection: Object, Reflect, Proxy
  • JSON / Math / Date / RegExp
  • Free functions: isFinite, isNaN, parseInt, parseFloat, encodeURIComponent, decodeURIComponent, encodeURI, decodeURI

Array<T>, Promise<T>, Record<K, V>, and structural object types all work as you'd expect. No any-everywhere required.

Web platform — fetch, URL, encoding, timers, abort

These are exposed by Bun's runtime and explicitly declared in the typecheck runtime-globals shim:

  • fetch, Request, Response, Headers, RequestInit, ResponseInit, Blob, FormData
  • URL, URLSearchParams
  • setTimeout, clearTimeout, setInterval, clearInterval, setImmediate, clearImmediate, queueMicrotask
  • AbortController, AbortSignal
  • TextEncoder, TextDecoder, atob, btoa, structuredClone
  • crypto.randomUUID(), crypto.getRandomValues(...), crypto.subtle

The fetch available in the runtime IS the one Bun provides — same shape as Node 18+'s undici fetch. Prefer ctx.stdlib.fetch / ctx.stdlib.fetchJson for built-in retries; bare fetch works too.

For external APIs that need secrets, prefer script connections for typed ctx.api / ctx.mcp access, or the scripts credential broker when you truly need a hand-written fetch(). In both cases, avoid putting raw tokens in source or args.

Console + logger

console.log / .warn / .error / .info / .debug are global. They also hang off ctx.logger. Both write to the subprocess stderr (captured by the executor and returned in the HTTP response's stderr field).

Node-compat surface

Bun's Node compatibility layer makes these available:

  • Buffer (Uint8Array-compatible, with Buffer.from(...), Buffer.concat(...), etc.)
  • process.env — typed as Record<string, string | undefined>. The environment is stripped to a small allowlist before the user script runs. You should NOT assume any specific env keys exist. Today's surviving keys: HOME, LANG, LC_ALL, PATH, TMPDIR, SWARM_SCRIPT_* (internal).
  • process.platform, process.arch, process.version, process.cwd(), process.hrtime()

Swarm-specific imports

import { fetch, fetchJson, grep, glob, table, Redacted } from "stdlib";
// stdlib also flows through ctx.stdlib at runtime
import type { ScriptContext, ScriptMain, SwarmConfig, Redacted } from "swarm-sdk";

The ScriptContext passed to your default export carries:

  • ctx.swarm — typed SDK for in-swarm operations (memory, kv, tasks, scripts, repos, schedules). Method allowlist: see src/scripts-runtime/sdk-allowlist.ts.
  • ctx.swarm.configapiKey, agentId, mcpBaseUrl (all wrapped in Redacted<string>; unwrap via ctx.stdlib.Redacted.value(...)).
  • ctx.stdlib — the runtime helpers (fetch, fetchJson, grep, glob, table, Redacted).
  • ctx.logger — Console-compatible logger; same destination as the global console.

Allowed bare imports

The TypeScript import allowlist (src/scripts-runtime/import-allowlist.ts):

  • stdlib
  • swarm-sdk
  • zod — for declaring export const argsSchema = z.object({...})

import "fs" / "node:fs" / "path" / any unlisted bare specifier is rejected at upsert and at run time.

What the runtime does NOT provide

These are rejected by the typecheck and would also fail at runtime — they do NOT exist in the script sandbox:

  • DOM APIs — window, document, localStorage, sessionStorage, HTMLElement, Event, etc. The typecheck does NOT include lib.dom.d.ts. We did this on purpose; the runtime is not a browser.
  • Filesystem — fs, node:fs, node:fs/promises. Use ctx.stdlib.glob and ctx.stdlib.grep for read-only file inspection in workspace-rw mode (v2 only).
  • Subprocess — node:child_process, Bun.spawn. Scripts cannot shell out.
  • Net — node:net, raw TCP/UDP. Outbound HTTP via fetch only.
  • Bun.* globals — even though Bun is the runtime, scripts cannot access the Bun object directly. Use ctx.stdlib instead.

Typecheck diagnostics

When script-upsert rejects code, the response is structured:

{
  "error": "typecheck_failed",
  "diagnostics": ["...colorized full diagnostic..."],
  "structured": [
    {
      "severity": "error",
      "code": 2552,
      "message": "Cannot find name 'Mat'. Did you mean 'Math'?",
      "file": "/virtual/user-script.ts",
      "line": 1,
      "column": 28,
      "endLine": 1,
      "endColumn": 31,
      "identifier": "Mat",
      "suggestion": "Math"
    }
  ]
}

Each entry carries the TypeScript diagnostic code (TS2552, TS2304, …), the precise location, the offending identifier when it's a name lookup, and the compiler's "did you mean…" hint when one is offered.

Runtime errors

When a script throws at runtime, the HTTP response includes a runtimeError field beside stderr:

{
  "exitCode": 1,
  "error": "eval_error",
  "stderr": "Error: kaboom from line 4\n    at user-script.ts:4:13",
  "runtimeError": {
    "name": "Error",
    "message": "kaboom from line 4",
    "stack": "Error: kaboom from line 4\n    at .../user-script.ts:4:13\n    ...",
    "userFrames": [{ "file": "user-script.ts", "line": 4, "column": 13, "raw": "at user-script.ts:4:13" }],
    "userScriptLine": 4,
    "userScriptColumn": 13
  }
}

Stack frames inside the harness or node_modules are stripped from the stderr text shown to clients (they remain in runtimeError.stack for debugging). The user-script path is normalized to the basename user-script.ts — absolute tmpdir paths never leak.

Exposing a script externally

A saved script can be opted in to a public HTTP endpoint — POST /api/x/script/<id> — for callers outside the swarm, with optional bearer auth and typed input validation against the same argsJsonSchema this page describes.

When to escalate

If you find code that runs fine in the runtime but fails the typecheck, treat it as a swarm bug, not a script bug. Add a probe to src/tests/scripts-typecheck.test.ts and either expand the runtime-globals shim (SCRIPT_RUNTIME_GLOBALS in src/be/scripts/typecheck.ts) or open an issue with the failing snippet.

On this page