EnvironmentBindings is the callback surface cyrnel hands to an environment
module at activation. The environment calls into these to talk to the
host.
interface EnvironmentBindings {
invokeTool(input: InvokeInput): Promise<unknown>;
setState(eid: number, data: ExecutionState): void;
setError(eid: number, data: string): void;
emitStdout(eid: number, data: Buffer): void;
emitStderr(eid: number, data: Buffer): void;
emitOutput(eid: number, data: Record<string, unknown>): void;
}
User code drives tool execution through the environment’s runtime surface
(cyrnel.services[id].tools[t].invoke(...)), which proxies through to
this callback.
Execution Reporting
The environment must report execution state via these callbacks. The
host uses them to keep the process record in sync with what’s actually
happening.
| Callback | When to call |
|---|
setState(eid, "queued") | Execution accepted but not yet running. |
setState(eid, "running") | Execution started in the runtime. |
setError(eid, message) | Execution failed; supply a human-readable message. The host writes this to process.error. |
emitStdout(eid, buf) | User code wrote to stdout. The host append-decodes. |
emitStderr(eid, buf) | User code wrote to stderr. |
emitOutput(eid, obj) | User code emitted a structured output payload. The host Object.assigns into the running output. |
eid always matches the process pid, it’s the value the host passed
in ExecutionInput.eid. Mixing up eid values silently mis-routes
output between processes.
Buffers and Strings
emitStdout and emitStderr take Buffer. The host owns the decode.
It runs each stream through a StringDecoder("utf8") so multi-byte
characters split across chunks are reassembled correctly. The
environment should pass raw bytes; do not decode and re-encode.
emitOutput takes a plain object. The host treats it as a partial patch
(Object.assign), so subsequent emits with the same key overwrite, and
new keys are appended.
Implementation Notes
- Idempotency:
setState calls are advisory. The host ignores
duplicate transitions (running → running) and rejects nonsensical
ones (idle → running). Don’t rely on every call landing.
- Ordering: callbacks happen in whatever order the environment
invokes them. The host does not buffer or reorder. If you need
ordering guarantees with respect to execution completion, finish the
callbacks before resolving
execute.
- Thread safety: these are plain functions on the host’s event loop.
An environment using worker threads must marshal calls back through
references (the bundled
typescript-ivm does this with
ivm.Reference).
Last modified on June 24, 2026