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