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:
{
"$schema": "https://raw.githubusercontent.com/actelos/cyrnel/main/schema/module.schema.json",
"id": "my-adapter",
"name": "My Adapter",
"type": "adapter",
"description": "Talks to my service",
"main": "./index.js"
}
Fields:
id: Unique identifier for the module.
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).
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
-
Place the built directory at
$CYRNEL_DATA_DIR/modules/my-adapter/.
-
Ensure it contains
module.json and the file named in main.
-
Either restart the API or call:
curl -X POST http://localhost:9371/modules/reload
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
curl http://localhost:9371/modules
You should see your module alongside the built-ins, with
isBuiltin: false.
To explicitly enable / disable:
curl -X POST http://localhost:9371/modules/my-adapter/enabled \
-H 'content-type: application/json' \
-d '{"enabled":true}'
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:
import type { ModuleExport } from "@cyrnel/sdk";
// For adapter modules:
import type { AdapterModule } from "@cyrnel/sdk";
export default {
configSchema: { /* JSON Schema for module-level config */ },
secretsSchema: { /* JSON Schema for module-level secrets */ },
instantiate(): AdapterModule {
return new MyAdapter();
},
} satisfies ModuleExport;
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:
- Build the module (
tsc, esbuild, whatever).
cp -r dist $CYRNEL_DATA_DIR/modules/<id>/.
POST /modules/reload.
- 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 July 1, 2026