An environment module runs the code clients submit through
POST /processes. It owns:
- Code execution. Transpiling, sandboxing, and running submitted code.
- Runtime bindings. Exposing the
cyrnel.* API (or whatever shape it
prefers) so user code can discover and invoke tools.
- Output capture. Streaming stdout, stderr, and structured output back
to the host through callbacks.
- 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
setup({ bindings }) is called when the environment is activated.
bindings is the EnvironmentBindings callback
surface back into the host (discovery, invocation, state, output).
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.
kill(eid) interrupts a running or queued execution. Should
return promptly.
generateDocs() returns a Markdown string describing the
environment’s globals. Surfaced by GET /environment/docs.
generateToolDocs(input) returns Markdown describing how to call a
single tool inside this environment. Surfaced by
GET /tools/:serviceId/:toolId/docs.
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