Skip to main content
This page covers the environment-specific parts of writing a custom module. For the shared mechanics (file layout, registration, lifecycle) see Writing a custom module. For the full interface reference see EnvironmentModule.

Minimal Skeleton

import type {
  EnvironmentBindings,
  EnvironmentModule,
  EnvironmentSetupContext,
  ExecutionExitState,
  ExecutionInput,
  ToolDocsInput,
} from "@cyrnel/sdk";

class MyEnvironment implements EnvironmentModule {
  private bindings!: EnvironmentBindings;

  async setup({ bindings }: EnvironmentSetupContext) {
    this.bindings = bindings;
  }

  async teardown() {}

  async execute(input: ExecutionInput): Promise<ExecutionExitState> {
    this.bindings.setState(input.eid, "running");
    try {
      // ...execute input.code in your runtime, emitting stdout/stderr/output
      return "success";
    } catch (err) {
      this.bindings.setError(input.eid, String(err));
      return "failed";
    }
  }

  async kill(_eid: number) {}

  async generateDocs() {
    return "# My Environment\n\nDocument the runtime here.";
  }

  async generateToolDocs(_input: ToolDocsInput) {
    return "# Tool\n\nDocument how to call this tool here.";
  }
}

const configSchema = { type: "object", properties: {} } as const;
const secretsSchema = { type: "null" } as const;

export default { configSchema, secretsSchema, instantiate: () => new MyEnvironment() };

Full Example

See the Shell environment example in the repository for a complete, working environment that executes submitted code as shell commands via sh -c. It streams stdout and stderr back through the host bindings and reports the exit code on completion. See EnvironmentBindings for the callback surface and Execution for the state machine and timeout contracts.
Last modified on June 24, 2026