Skip to main content
This page covers the mechanics shared by every custom module, registration, and lifecycle. For type-specific skeletons and examples see: For the full interface reference, see AdapterModule and EnvironmentModule.

Installation

To use the cyrnel SDK in your TypeScript codebase, install the @cyrnel/sdk package:

Where Modules Live

Custom modules live under $CYRNEL_DATA_DIR/modules/<id>/. Each directory must contain a module.json:
Fields:
  • id: Unique identifier for the module.
  • version: Semver version of the module (required, e.g. "1.2.3").
  • name: Human readable name for the module.
  • type: adapter or environment.
  • description: Surfaced in GET /modules.
  • main: Path to the built module file the host can import(). The file must default-export the module object (see below).
  • engines (optional): Object declaring the Cyrnel core version range this module supports. When present, the cyrnel field is required: it must be a semver range (e.g. ">=3.0.0") that the Cyrnel core version must satisfy, or the module is rejected at registration time. Omitting engines entirely is allowed and means “no core version constraint”.
The full JSON Schema is available at schema/module.schema.json. You can reference it from your module.json via $schema for editor autocompletion and validation. The host expects compiled JavaScript here, not TypeScript. The expectation is that you ship modules built ahead of time.

Registering a Module

  1. Place the built directory at $CYRNEL_DATA_DIR/modules/my-adapter/.
  2. Ensure it contains module.json and the file named in main.
  3. Either restart the API or call:
reload re-scans $CYRNEL_DATA_DIR/modules/, registers any new directories, and reconciles the result against the modules table. A new module is inserted with enabled: true, missing: false by default.

Verifying

You should see your module alongside the built-ins, with isBuiltin: false. To explicitly enable / disable:
For adapters, enabling triggers setup({ config, secrets }) and hydration of any enabled services that target it. For environments, enabling makes it the active environment and drains the previous one.

Removing a Module

Stop the API, delete $CYRNEL_DATA_DIR/modules/<id>/, and restart. Or delete the directory and POST /modules/reload. The modules row is not deleted, it’s marked missing: true. The enabled flag is preserved so the module resumes at its previous state if it reappears. Trying to enable a missing module returns 409.

Module ID Collisions

If a custom module declares a name that already exists (built-in or otherwise registered), the host keeps the first registration and skips the duplicate. Built-ins are registered first.

Module Entry File

The file pointed to by main must default-export an object with the following shape:
  • configSchema and secretsSchema must be plain JSON-only objects. No functions, class instances, Proxies, symbols, or circular references. The host validates this at registration time.
  • instantiate is a zero-argument factory that returns a fresh module instance. The host calls it each time the module is activated.
For the type-specific skeletons see:

Iteration Workflow

While developing:
  1. Build the module (tsc, esbuild, whatever).
  2. cp -r dist $CYRNEL_DATA_DIR/modules/<id>/.
  3. POST /modules/reload.
  4. Disable + re-enable the module to force teardown / setup.
The reload endpoint refreshes the registry but does not re-activate already-enabled modules. Toggle enabled to pick up code changes.

Packaging Notes

  • The host loads main with the native import(), it must be an ESM file the Node runtime can resolve directly. Bundle into a single file if your module has dependencies; the loader does not run npm install for you.
  • peerDependencies are not honoured. If your module needs @cyrnel/sdk types, ship them as build-time devDependencies and bundle anything you import.
  • Keep modules narrow. A single module that does many unrelated things is harder to review than several small ones.
Last modified on August 1, 2026