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

Logging

The host owns all logging - your module never creates a logger. The logger it receives in setup already carries your module’s identity (moduleId, moduleType, environmentId), and every entry is tagged type: "module".
  • Call context.logger.redact(patterns) to opt into self-managed reduction. Declare redactionPatterns in your own configSchema and read it back from context.config: the host never supplies patterns for you. Your patterns merge additively on top of a non-disableable baseline (secrets / tokens / passwords / authorization).
  • Use logger.child({ ... }) to scope a logger for a phase or a specific execution. child accepts only phase/event; the host-owned correlation fields (executionId, dispatchId, toolId, …) are merged in by the host and cannot be forged.
  • Emit structured logs with logger.info({ event, ...payload }, "message"). All six levels are available: trace, debug, info, warn, error, fatal.

envConfig

The host passes per-execution configuration via input.envConfig (a Record<string, unknown>). The keys are caller-provided; each environment module documents which keys it recognises and falls back to sensible defaults for any missing values:
Formerly this configuration lived in a separate ExecutionOptions type and an options field on ExecutionInput. Both have been removed in favour of envConfig. Update any existing module code accordingly.

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 August 25, 2026