Skip to content

About

A sandbox-first JavaScript engine for AI agents: host-set capabilities and limits, deterministic runs, TypeScript annotations, test262 on every pull request.

Topics

Resources

Contributing

Stars

20 stars

Watchers

1 watching

Forks

Latest commit

 

History

1,487 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GocciaScript

GocciaScript logo

A drop of JavaScript, sandboxed by default.

GocciaScript is a JavaScript engine for running code that AI agents and people write, under the capabilities and limits the host sets. A script sees no filesystem, network or process unless the host grants it (permissions, capability audit events), and a run can be bounded by time, instructions and memory and made deterministic, with a fixed clock and seeded randomness.

test262 pass rate on main, live from the compatibility dashboard CI Latest release

  • Conformance you can check. test262, the official ECMAScript conformance suite, runs on every pull request and on main, and a pull request that turns a passing test into a failure fails CI. The compatibility dashboard shows the current result by area, rendered from the published reports.
  • Tested beyond the spec. 15,000+ JavaScript tests in 1,800+ files run on every pull request, and on six targets on main, with differential suites checked against Bun and Vitest.
  • Fuzzed and memory-checked. Every night the engine and its heap snapshot reader are fuzzed, and the full test suite runs under FreePascal's heap checker and under Valgrind memcheck. Every week a GC-stress run collects garbage every 8 instructions across the whole test suite and the GC-sensitive part of test262.
  • In production. GocciaScript is the engine behind Shotcup's hosted sandboxes, where every run gets a fresh engine with hard limits.
  • Native, with nothing underneath. Its own compiler, bytecode VM and garbage collector, built with FreePascal into native binaries: no Node.js, V8 or other runtime to install beside the binary (HTTPS on Linux uses the system's OpenSSL). Prebuilt for macOS and Linux (x64 and arm64) and Windows (x64 and x86), and embeddable in FreePascal applications.
  • Modern JavaScript and TypeScript. ES modules, classes with private fields, async/await, Temporal, Intl, TC39 type annotations run directly, and a Vitest-compatible test runner.
  • Status: pre-1.0. The language follows ECMAScript; the host API and command-line flags may change between releases, so pin a version and read the changelog before you upgrade.
curl -fsSL https://www.gocciascript.dev/install | sh

On Windows, irm https://www.gocciascript.dev/install.ps1 | iex; on macOS, also brew install frostney/tap/gocciascript. Or try it in the Playground. Building from source needs the FreePascal compiler; see Getting Started.

It is not trying to become Node.js or a browser host: the primary goal is running agent code under an explicit host-defined capability model, and native embedding is the second.

Start with an agent sandbox

GocciaRunner's sandbox mode copies explicit host paths into an in-memory virtual filesystem, runs an entry script with host-owned limits, and reports sandbox changes as a diff. Copies are snapshots, not live mounts, and scripts receive no ambient host filesystem access.

// agent-workspace/main.js
import fs from "fs";

const input = fs.readFileSync("/task.txt", "utf8");
fs.mkdirSync("/out", { recursive: true });
fs.writeFileSync("/out/result.txt", input.toUpperCase());
GocciaRunner agent-workspace/main.js \
  --copy agent-workspace/task.txt \
  --timeout=5000 \
  --diff

(From a source build, the binary is ./build/GocciaRunner after ./build.pas runner.)

The entry file lands at /main.js and the copied input at /task.txt. Nothing the script writes reaches the host unless the host asks for it: an input copied with --copy-rw instead of --copy has the files a successful run changed inside it written back to their host paths, and everything else in the virtual filesystem is discarded. A program that only reports and a program that fixes are therefore the same program, and the difference is a word on the host's command line. See Permissions — Sandbox mode.

The host can also define globals, virtual modules, network grants (--allow-net), instruction and memory limits, deterministic time/randomness, and application-specific APIs. See Build System — GocciaRunner sandbox mode and Built-ins — Sandbox Modules.

ECMAScript implementation and recommended profile

GocciaScript implements core ECMAScript and runs the official test262 corpus on every PR and on main. The exact current result is rendered from published main-branch data on the live ECMAScript compatibility dashboard; canonical prose does not freeze a historical percentage.

The recommended language profile is modern by default, so code an agent writes stays explicit, and each core form it disables has a flag. It is product policy, not the implementation ceiling. Standard core forms disabled by default all have explicit compatibility paths:

Form Default Enablement / exposure
var Disabled --compat-var
traditional function syntax Disabled --compat-function
== / != Disabled --compat-loose-equality
ASI Disabled --compat-asi
labels Disabled --compat-label
traditional for(;;) Disabled --compat-traditional-for-loop
for...in Disabled --compat-for-in-loop
while / do...while Disabled --compat-while-loops
arguments Disabled --compat-arguments-object
non-strict Script semantics and with Strict / disabled --compat-non-strict-mode
eval Not installed by normal hosts private GocciaTest262Runner conformance host
Function() Disabled --unsafe-function-constructor
ShadowRealm Not installed --unsafe-shadowrealm

Annex B's browser-only legacy surface is not a general pre-1.0 target; see ADR 0085. See Language and Language Tables for the detailed semantics and feature matrix.

TC39 Type Annotations and --strict-types

GocciaScript implements the official TC39 Type Annotations proposal and its types-as-comments runtime model. Supported annotations have no runtime effect by default. GocciaScript additionally provides the optional --strict-types extension, which enforces supported annotations and relevant inferred primitive contracts at runtime.

--strict-types is a runtime contract extension, not a replacement for a static structural type checker such as tsc. See Type Annotations for the supported syntax and its parsing rules.

Node host compatibility and sandbox fs

GocciaScript is not a complete Node.js host: it does not provide CommonJS, process, Buffer, or the general node: module set. Bare-specifier lookup through node_modules (ESM only, a subset of Node's resolver) is opt-in with --allow-import=node_modules; see Module Resolution. GocciaRunner's sandbox mode does provide a Node-compatible fs API over its virtual filesystem:

  • synchronous forms such as readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, rmSync, renameSync, and copyFileSync;
  • Node-shaped callback forms for the same operation families;
  • fs.promises forms, Stats objects, and Node-shaped filesystem errors;
  • fs.globSync, fs.glob and fs.promises.glob for names, with content and fuzzy-name search in goccia:search (Sandbox Search Built-ins).

The documented method set never reaches the ambient host filesystem. See Sandbox Modules for supported options, return types, error shapes, and method-level deviations. An embedder can mark parts of the virtual filesystem read-only; a script's writes there fail with EROFS (see Embedding — Sandbox filesystem).

Runtime and toolchain features

The default profile includes modern ECMAScript forms such as let/const, arrow functions, classes with private fields, for...of, async/await, ES modules, decorators, and proposal-compatible type syntax.

Built-in Objects

Core built-ins include Math, JSON, Object, Function, Array, Boolean, Number, BigInt, String, RegExp, Symbol, Set, Map, WeakSet, WeakMap, WeakRef, FinalizationRegistry, Promise, Temporal, Intl, Iterator, DisposableStack, AsyncDisposableStack, Proxy, Reflect, ArrayBuffer, SharedArrayBuffer, DataView, Atomics, and TypedArrays (Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float16Array, Float32Array, Float64Array, BigInt64Array, BigUint64Array), alongside the global functions queueMicrotask, structuredClone, atob, and btoa. The loader runtime profile (applied by GocciaRunner, GocciaREPL, GocciaTestRunner, and GocciaBenchmarkRunner) adds console, performance, fetch, Headers, Response (WHATWG Fetch — GET/HEAD only), AbortController, AbortSignal, EventTarget, Event, URL, URLSearchParams, TextEncoder, and TextDecoder. Error constructors include Error, EvalError, TypeError, ReferenceError, RangeError, SyntaxError, URIError, AggregateError, SuppressedError, and DOMException.

Non-standard data-format APIs and SemVer are import-only Goccia runtime modules, not auto-installed globals: goccia:csv, goccia:json5, goccia:jsonl, goccia:toml, goccia:tsv, goccia:yaml, and goccia:semver. They expose named exports only; use import * as CSV from "goccia:csv" when you want the namespace-object shape. There is no default export.

goccia:ast is an experimental runtime module behind --experimental-ast. It exposes one parse function that returns a source file's statement structure — kinds, offsets, line/column, nesting — plus its comments and, on request, every token, so a lint rule, a codemod or a check that needs to know what is code can be a GocciaScript program. Every offset and position is into the text that was passed, including for a .tsx file the engine rewrote before parsing. See Built-ins, ADR 0117, ADR 0118, and ADR 0163.

node:async_hooks is an import-only module too, at Node's own address. It exports AsyncLocalStorage and AsyncResource, named and on the default export; the async_hooks observer API (createHook, executionAsyncId, and the rest) is out of scope. The engine propagates the async context, so a store bound with run survives await and every promise-reaction continuation. See the Async Context reference and ADR 0112.

Native FFI needs an explicit ffi grant (--allow-ffi[=<library>,...] or "allow-ffi" in a config file's permissions block). It provides native-layout structures, unions, fixed-length arrays, callbacks, and guarded library lifetimes through GocciaScript's custom bidirectional ABI machinery. See the FFI reference and ADR 0095.

See Built-in Objects for the complete API reference.

Example

class CoffeeShop {
  #name = "Goccia Coffee";
  #beans = ["Arabica", "Robusta", "Ethiopian"];
  #prices = { espresso: 2.5, latte: 4.0, cappuccino: 3.75 };

  getMenu() {
    return this.#beans.map((bean) => `${bean} blend`);
  }

  calculateTotal(order) {
    return order.reduce((total, item) => total + (this.#prices[item] ?? 0), 0);
  }

  get name() {
    return this.#name;
  }
}

const shop = new CoffeeShop();
const order = ["espresso", "latte"];
const total = shop.calculateTotal(order);

console.log(`Welcome to ${shop.name}!`);
console.log(`Your order total: $${total.toFixed(2)}`);

Getting Started

Install

Prebuilt binaries ship with every release and every nightly for macOS and Linux (x64 and arm64) and Windows (x64 and x86):

# macOS and Linux
curl -fsSL https://www.gocciascript.dev/install | sh

# a pinned version
curl -fsSL https://www.gocciascript.dev/install | GOCCIA_VERSION=0.15.0 sh

# macOS with Homebrew
brew install frostney/tap/gocciascript
# Windows PowerShell
irm https://www.gocciascript.dev/install.ps1 | iex

The installers put GocciaRunner, GocciaTestRunner and GocciaREPL on your PATH. Each release archive carries the whole toolchain; see the installation guide. The binaries need only libraries the operating system provides: HTTPS uses the platform's TLS stack (OpenSSL on Linux), and Intl uses the system ICU when present and embedded data otherwise.

Build from source

Building needs the FreePascal compiler (fpc), version 3.2.2 or 3.2.4. The package managers install 3.2.2:

  • macOS: brew install fpc
  • Ubuntu/Debian: sudo apt-get install fpc
  • Windows: choco install freepascal

CI is pinned to the FPC 3.2.4 build 3.2.4-rc2-b1 from fpc-toolchain. To build with the same compiler on Linux or macOS, install it next to any other FPC and put it first on PATH (Windows: see its README):

curl -fsSLO https://raw.githubusercontent.com/frostney/fpc-toolchain/main/scripts/install.sh
sh install.sh 3.2.4-rc2-b1
eval "$(~/.local/share/fpc-toolchains/bin/fpc-toolchain env 3.2.4)"
# Dev build of everything
./build.pas

# Production build
./build.pas --prod

# Build the runner only
./build.pas runner

See Build System for build modes, targets, clean builds, and troubleshooting. The commands below use ./build/<tool> from a source build; with an installed release, drop the ./build/ prefix.

Run a Script

./build.pas runner && ./build/GocciaRunner example.js
printf "const x = 2 + 2; x;" | ./build/GocciaRunner --print

By default, the runner is silent about the script's last evaluated value. Pass --print to emit it; use --output=json for programmatic consumers. See Build System for runner options, JSON output, sandbox execution, import maps, config files, and resource limits.

Run via Bytecode

Scripts, tests, benchmarks, and the REPL compile to bytecode and run in the Goccia VM, and GocciaBundler compiles source to the public .gbc artifact.

./build/GocciaRunner example.js
./build/GocciaBundler example.js
./build/GocciaRunner example.gbc

See Bytecode VM for the compiler and VM architecture. Module artifacts can also satisfy imports: use GocciaRunner --prefer-bytecode app.mjs for disk companions, or register cached buffers through the embedding API. Loading checks that the artifact's compile profile matches the engine.

Reproduce An Execution

Use one fixed JavaScript-visible clock, UTC time zone, and portable random stream:

./build/GocciaRunner example.js --deterministic

Timeouts and profiling still use the real monotonic clock. The equivalent config key is "deterministic": true; embedders can inject their own clock and RNG providers through the engine host environment.

For custom providers, pass a JavaScript module to --host-environment or implement the Pascal host interfaces. See Host Environment for both examples and the provider contract.

Start the REPL

./build.pas repl && ./build/GocciaREPL

Run Tests

GocciaScript has 15,000+ JavaScript end-to-end tests across 1,800+ test files, covering language features, built-in objects, and edge cases.

Some test directories request permissions in their configs, so accept them for the run with -P (or trust the suite once per checkout with ./build/GocciaTestRunner --trust tests/ and drop -P):

./build.pas testrunner
./build/GocciaTestRunner -P tests

The test runner supports Vitest-compatible external and inline snapshots, property shapes, asymmetric matchers, custom serializers, and -u updates. Importing vi from "vitest" resolves to a bundled compatibility shim, so suites written against vi.fn, vi.spyOn, and factory-form vi.mock (a synchronous arrow factory returning an object literal — no automock, no spread-based partial mock) run unmodified; see Test Framework API for the full factory constraints and the members that are not implemented. See Testing for test organization and Build System for runner options.

Run Benchmarks

./build.pas benchmarkrunner && ./build/GocciaBenchmarkRunner benchmarks
./build/GocciaBenchmarkRunner benchmarks/fibonacci.js

The benchmark runner auto-calibrates iterations per benchmark, reports ops/sec with variance (CV%) and engine-level timing breakdown (lex/parse/execute). Output formats: console (default), text, csv, json, compact-json (the same envelope as json without build, memory, stdout, or stderr). Calibration and measurement parameters are configurable via environment variables. Retained AWFY and JetStream reference measurements are published through the Performance Barometer.

Recommended-profile quick tour

The recommended profile favors modern, explicit forms. The corresponding core ECMAScript forms remain implemented through the compatibility paths listed above:

  • Arrow functions and methods by default — const greet = (name) => `Hello, ${name}!`;; traditional function syntax uses --compat-function.
  • Iterator-oriented loops by default — use array methods, iterators, or for...of; traditional for(;;), for...in, while, and do...while each have targeted compatibility flags.
  • Classes with private fields — class Account { #balance = 0; ... }
  • ES modules — default, named, and namespace imports/exports are supported; project code prefers named exports for clarity.
  • Strict equality by default — === and !== (==/!= require --compat-loose-equality)

Beyond the static imports of its own project a script reaches nothing by default: other host reads, computed dynamic imports, fetch, FFI, and node_modules all throw PermissionDenied until the host grants them with --allow-read, --allow-net, --allow-ffi, or --allow-import (or a config file's permissions block). A deny always wins. See Permissions.

A config file's permissions block is only a request: its grants take effect once the user trusts the config (--trust <path>) or accepts it for one run (-P). A block that only denies needs no trust. See Permissions — Config trust.

The CLI tools share WHATWG-style import map support with --import-map=<file.json>, --alias key=value, and automatic goccia.json discovery for project-level module aliases. Host-supplied dependencies should normally be configured as virtual ES modules with --module, --modules, or a config modules object; they participate in the same import pipeline as filesystem modules. Global injection remains supported for compatibility.

An import-map entry can also name a github: provider package. GocciaRunner --add and --install pin it in goccia.lock.json, and a run loads it once the import capability grants it (--allow-import=github). See Provider Imports.

Structured data files and text assets can also be imported directly:

import { name, version } from "./package.json";
import { name as packageName } from "./config.toml";
import { name as appName } from "./config.yaml";
import { content, metadata } from "./README.md";

Runtime parsers are available through named Goccia modules for JSON5, TOML, YAML, JSONL, CSV, and TSV. See Built-in Objects and Language for the full data format reference.

import * as TOML from "goccia:toml";
import * as YAML from "goccia:yaml";

TOML.parse(sourceText); // TOML 1.1.0 configuration data
YAML.parse(sourceText); // block scalars, anchors/aliases, merge keys, YAML 1.2 tags

See Language and Architecture Decision Records for the full conformance details.

JSONL parsing is also available from goccia:jsonl via parse(text) and parseChunk(text), and .jsonl files can still be imported as structured-data modules.

Async/await with full Promise support, including top-level await:

const fetchData = async () => {
  const result = await Promise.resolve({ status: "ok" });
  return result;
};

// Top-level await (ES2022+)
const data = await fetchData();

Strict equality by default — === and !==; == and != are available only with --compat-loose-equality.

For a full guided walkthrough, see the Tutorial. For the complete implementation, default-profile, and compatibility-path detail, see Language.

FreePascal, Delphi, and native embedding

FreePascal builds GocciaScript's native binaries, so the engine needs no other runtime underneath, and it is the toolchain for releases and the documented embedding API. The repository also includes GocciaScript.Delphi.groupproj and Delphi projects for the REPL, the Runner, the Bare Script Loader, Test Runner, Benchmark Runner, and Bundler on Win32 and Win64.

The Delphi support contract requires the complete application matrix and all applicable Pascal and JavaScript tests to pass with the same runtime semantics as the FreePascal build. Delphi 12 Community Edition contributors validate that contract through the IDE; see Delphi Validation and Build System.

Native hosts can embed GocciaScript to run portable JavaScript with application-specific globals, modules, capabilities, and limits. A host can answer fetch through its own transport, mark sandbox paths read-only, and snapshot a paused run's heap to restore it in a fresh engine (Heap Snapshots). See Embedding the Engine.

Architecture

GocciaScript runs every program through one pipeline: the source pipeline (preprocessors, lexer, parser) builds an AST, the compiler turns it into Goccia bytecode, and the Goccia VM runs it:

flowchart LR
    Source["Source Code"] --> Preprocessors["Preprocessors"] --> Lexer --> Parser --> AST
    AST --> Compiler["Bytecode Compiler"] --> VM["Goccia VM"] --> Result["Result"]
Loading

The VM, the built-ins and the runtime share one set of value types, one scope chain, and one mark-and-sweep GC. The VM is Goccia-owned rather than a generic VM layer: it keeps tagged TGocciaRegister values (unboxed scalars) and falls back to TGocciaValue for heap objects. Releases up to 0.14 also offered a tree-walk interpreter as a second execution mode; GocciaScript 0.15.0 removed it (ADR 0138).

See Architecture for the pipeline and layers, Bytecode VM for bytecode execution, Core patterns for implementation patterns, and GocciaScript Context for canonical terminology.

Design Principles

  • Explicitness: Modules, classes, methods, and properties use explicit, descriptive names even at the cost of verbosity. Shortcuts are avoided.
  • OOP over everything: Rely on type safety of specialized classes rather than generic data structures.
  • Define vs Assign: Define creates a new variable binding; Assign changes an existing one. These are distinct operations throughout the codebase (see Core patterns).
  • No global mutable state: All runtime state flows through explicit parameters — the evaluation context, the scope chain, and value objects.
  • Virtual dispatch: Property access (GetProperty/SetProperty), type discrimination (IsPrimitive/IsCallable), and scope lookups (GetThisValue, ResolveIdentifier) all use virtual methods, replacing type checks with single VMT calls.

See Core patterns and Architecture for the design rationale.

Documentation

Document Description
Vision Why GocciaScript exists: sandboxed AI agent runtime and embeddable desktop platform
Tutorial Your first GocciaScript program — a guided walkthrough for newcomers
Language ECMAScript support, recommended defaults, compatibility flags, and rationale
Language Tables Quick-reference: ECMAScript feature matrix and TC39 proposal status
Type Annotations TypeScript-compatible type syntax, --strict-types, and the < disambiguation rules
Built-in Objects Available built-ins and API reference
FFI Built-ins Native libraries, aggregate types, callbacks, lifetimes, and safety limits
Temporal Built-ins Temporal API: dates, times, durations, time zones
Binary Data Built-ins ArrayBuffer, SharedArrayBuffer, TypedArray API
Sandbox Search Built-ins fs.globSync, and grep and fuzzyFind from goccia:search, with their shared test vectors
Async Context node:async_hooks: AsyncLocalStorage, AsyncResource, and what propagates
Errors Error types, parser/runtime display, Error.cause, try/catch/finally
JSON Output The --output=json and --output=compact-json envelopes, including errors and timeouts
Architecture Pipeline, main layers, scopes, errors, microtask queue, design direction
Bytecode VM Compiler, opcodes, register VM, .gbc format
Core patterns Recurring implementation patterns
GocciaScript Context Canonical project terminology and glossary
Value System Type hierarchy, virtual property access, primitives, objects
Garbage Collector Mark-and-sweep GC: architecture, contributor rules, design rationale
Adding Built-in Types Step-by-step guide for adding new built-in types
Embedding the Engine Embedding GocciaScript in FreePascal applications
Heap Snapshots Writing and restoring heaps, the pause checkpoint, codecs, and --heap-snapshots test diagnostics
Module Resolution Resolution order, opt-in node_modules lookup, and the deviations from Node
Provider Imports github: import-map entries, goccia.lock.json, the .goccia cache, and verify on load
Virtual Module Configuration CLI, config-file, and embedding reference for host-supplied modules
Host Environment Injecting JavaScript-visible clock, time-zone, and random providers
Permissions The capability model: read, net, ffi, import, deny-wins, the module-graph exemption, PermissionDenied
Capability Audit Events Structured host capability decisions, embedding sink, and CLI JSONL output
Testing Test organization, running tests, coverage, CI
Test Framework API Assertions, mocks, lifecycle hooks, async patterns
Benchmarks Benchmark runner, output formats, writing benchmarks
Build System Build commands, compiler configuration, CI/CD
Profiling Bytecode VM profiling: opcodes, functions, output formats
Architecture Decision Records Durable architectural decisions and trade-offs
Contributing Single contribution standard: workflow, mandatory rules, testing, FreePascal style
AGENTS.md Agent operating manual for coding assistants; CONTRIBUTING.md is the contributing guide for everyone

Contributing

CONTRIBUTING.md is the contributing guide for all contributors (humans and AI): workflow, mandatory rules, testing, FreePascal code style, ./format.pas, editor setup, build/run quick reference, and the documentation index.

AGENTS.md (and CLAUDE.md, which imports it with @AGENTS.md) is only for AI assistants—how to use the repo and defer to CONTRIBUTING. It is not a second contributing guide and should stay short.

License

See LICENSE for details.

About

A sandbox-first JavaScript engine for AI agents: host-set capabilities and limits, deterministic runs, TypeScript annotations, test262 on every pull request.

Topics

Resources

Contributing

Stars

20 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages