> ## Documentation Index
> Fetch the complete documentation index at: https://actelos.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# EnvironmentModule

> The contract every environment module implements

```ts theme={null}
interface EnvironmentSetupContext extends ModuleSetupContext {
  bindings: EnvironmentBindings;
}

interface EnvironmentModule extends Module {
  setup(context: EnvironmentSetupContext): Promise<void>;
  execute(input: ExecutionInput): Promise<ExecutionExitState>;
  kill(eid: number): Promise<void>;
  generateDocs(): Promise<string>;
  generateToolDocs(input: ToolDocsInput): Promise<string>;
}
```

Environment modules execute submitted process code in a runtime they
own. See [Environment Modules](/cyrnel/docs/environments-modules) for the
user-facing description.

## Setup

`setup({ config, secrets, bindings })` is called when the environment is
activated. `config` and `secrets` carry the module-level settings from
the `modules` database row. `bindings` is the host's
[callback surface](/cyrnel/specs/environment-bindings), store it and use it to
report execution state, capture I/O, and forward tool discovery /
invocation requests.

The host activates **at most one environment at a time**. A previously
active environment is moved to draining state and torn down only after
its in-flight executions finish.

## `execute(input)`

```ts theme={null}
interface ExecutionInput {
  eid: number;
  code: string;
  options?: ExecutionOptions;
}

interface ExecutionOptions {
  timeoutMs: number;
}
```

Run one process. `input.eid` is the host-assigned execution id, it
matches the process `pid` exactly and **must** be the value passed to
every `bindings.*` callback so the host can route stdout, stderr, output,
and state changes to the right process record.

Return one of `"failed" | "success" | "timeout" | "canceled"` when
execution finishes. The host treats this as the final word; once
`execute` resolves, the process record transitions to `idle` with this
value as its `exitState`.

Inside `execute`, the environment is expected to:

1. Call `bindings.setState(eid, "queued")` if not already running, then
   `bindings.setState(eid, "running")` when the code starts.
2. Call `bindings.emitStdout(eid, buf)` / `emitStderr(eid, buf)` for any
   captured text output.
3. Call `bindings.emitOutput(eid, obj)` whenever user code emits a
   structured output payload.
4. Call `bindings.setError(eid, message)` if execution fails.
5. Resolve the promise with the final `ExecutionExitState`.

Throwing from `execute` is also acceptable, the host wraps it as
`"failed"` and records the message as the process `error`.

## `kill(eid)`

Interrupt a queued or running execution. The corresponding `execute`
promise should resolve as `"canceled"` (or close to it) shortly after
`kill` returns.

Should be safe to call for an unknown `eid` (return without error).

## `generateDocs()`

Return a Markdown string describing the runtime, globals available
inside, idioms, examples. Served at `GET /environment/docs`.

This is what AI clients read to learn how to write code for *this*
environment, so be specific. The bundled `typescript-ivm` documents its
`cyrnel.*` surface; a custom environment that exposes a different shape
should document the shape it exposes.

## `generateToolDocs(input)`

```ts theme={null}
interface ToolDocsInput {
  serviceId: string;
  toolId: string;
  description: string;
  inputSchema: JSONSchema;
  outputSchema: JSONSchema;
}
```

Return Markdown documenting a single tool **as it should be invoked
inside this environment**. Served at
`GET /tools/:serviceId/:toolId/docs`.

If the environment exposes tools as `cyrnel.services[s].tools[t].invoke(p)`,
render an example using that. If it exposes them as Python functions, do
that instead.

## Draining

When the host activates a different environment, the previous one is
**not** torn down immediately. It moves to a draining state:

* The host no longer dispatches new `execute` calls to it.
* In-flight executions continue.
* The host calls `kill(eid)` only when a process is explicitly killed.
* Once `execute` has resolved for every in-flight `eid`, the host calls
  `teardown()`.

Implementations should be safe to call after the active reference has
moved on, there is a brief overlap when both environments exist.

## Minimal Skeleton

```ts theme={null}
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.";
  }
}

export function instantiate(): EnvironmentModule {
  return new MyEnvironment();
}
```

> **Note:** The manifest metadata (name, version, type, etc.) goes in
> `module.json` for custom modules. Only built-in modules ship `export
> const manifest` from code. The skeleton above omits it, see
> [Writing a custom environment module](/cyrnel/specs/writing-custom-environment-module)
> for a step-by-step walkthrough and a full working example.

See [`EnvironmentBindings`](/cyrnel/specs/environment-bindings) for the callbacks and
[Execution](/cyrnel/specs/environment-execution) for the state machine details.
