Skip to main content
An environment module runs the code clients submit through POST /processes. It owns:
  1. Code execution. Transpiling, sandboxing, and running submitted code.
  2. Runtime bindings. Exposing the cyrnel.* API (or whatever shape it prefers) so user code can discover and invoke tools.
  3. Output capture. Streaming stdout, stderr, and structured output back to the host through callbacks.
  4. Cancellation and timeouts. Honouring kill(eid) and respecting input.envConfig.timeoutMs for an execution-level sandbox deadline. The host separately enforces an API-level timeout via kill(eid) when the timeoutMs field on POST /processes elapses.
Environment modules implement the SDK EnvironmentModule interface.

Lifecycle

  1. setup({ bindings }) is called when the environment is activated. bindings is the EnvironmentBindings callback surface back into the host (discovery, invocation, state, output).
  2. execute(input) is called for each process. input.eid is the process id; the environment must use it on every callback so the host can route stdout/stderr/output to the right process. input.code is the source code to run. input.envConfig is a Record<string, unknown> of per-execution configuration forwarded from the request. Each environment module documents its accepted envConfig keys.
  3. kill(eid) interrupts a running or queued execution. Should return promptly.
  4. generateDocs() returns a Markdown string describing the environment’s globals. Surfaced by GET /environment/docs.
  5. generateToolDocs(input) returns Markdown describing how to call a single tool inside this environment. Surfaced by GET /tools/:serviceId/:toolId/docs.
  6. teardown() is called when the environment is deactivated.

Timeout model

The API-level timeoutMs field on POST /processes is enforced by the host: the platform calls kill(eid) if the process exceeds the deadline. The environment module never sees this value directly; it only participates through kill(eid). input.envConfig.timeoutMs is an optional per-execution override that the environment module may use for its own internal deadlines (e.g. the TypeScript isolate’s sandbox timeout). The two levels are independent; the shorter of the two always wins.

Active environment vs draining

Only one environment is active at a time, the one whose modules row has enabled = true. Switching environments or disabling the active module moves it to a draining state: no new executions are dispatched to it, but in-flight ones run to completion. Once the last execution finishes, cyrnel calls teardown() on it. This means:
  • An environment’s execute may be called after another environment is active. Implementations must not assume “active” and “currently running executions” are the same set.
  • kill may be called during draining. Honour it.

Security Model

Environment modules own the security boundary user code runs inside. Their job, more than anything else, is to make sure user code cannot do anything the environment didn’t intend to expose. For the bundled typescript-ivm:
  • Code runs in an isolated-vm isolate with a hard memory limit.
  • The isolate has no module loader, no filesystem, no network, no Node built-ins. The only escape hatches are the references cyrnel installs.
  • Timeouts terminate the isolate; the worker slot is recreated rather than reused.
If you write a custom environment, the relevant questions are:
  • What can user code reach? (Network? Filesystem? Native code?)
  • Can a malicious process exhaust resources? (CPU loops, memory, file descriptors.)
  • Can one execution affect another? (Shared globals, persisted state.)
  • How are credentials exposed? (Adapter modules see secrets; environments should not.)
See Security for the broader picture.

Authoring

See Writing a custom environment module for the file layout, a skeleton, and a full working example. EnvironmentModule covers the interface contract in full, and EnvironmentBindings documents the callback surface.
Last modified on July 4, 2026