Skip to main content
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;
}

Tool Invocation

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.
CallbackWhen 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 (runningrunning) and rejects nonsensical ones (idlerunning). 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