Plugins
Plugins
Section titled “Plugins”Colony’s plugin system lets you add custom executor logic to a workflow without modifying Colony’s source code. A plugin is a Node.js module that exports a definePlugin(...) manifest. The worker loads registered plugins at startup and routes plugin:<name>/<executor-key> workflow references to the declared executor functions.
Authoring a plugin
Section titled “Authoring a plugin”defineExecutor
Section titled “defineExecutor”An executor is created with defineExecutor from @colony/sdk:
import { defineExecutor } from '@colony/sdk';
export const draftExecutor = defineExecutor({ outcomes: ['draft_created', 'needs_clarification', 'failure'] as const, run: async (ctx, task) => { // ... your logic here ... return { outcome: 'draft_created' }; },});| Field | Type | Description |
|---|---|---|
outcomes | readonly string[] | All outcome names this executor can return (non-empty, required) |
run | (ctx: ExecutorContext, task: TaskInfo) => Promise<ExecutorOutcome> | The executor function |
Reserved outcomes — the following names must not appear in outcomes. The SDK throws at definition time if any are declared:
| Name | Meaning |
|---|---|
noop | Completes the work task without transitioning the issue state |
retry | Re-enqueues the same task with a delay; issue state is unchanged |
error | Synthetic outcome the framework emits when the executor throws |
The outcomes array is load-bearing for workflow validation: the workflow engine cross-checks at load time that every workflow using this executor handles every declared outcome in its on: block. (colony workflow validate checks schema constraints; it does not cross-check outcome coverage — that check happens when the worker starts.)
definePlugin
Section titled “definePlugin”Assemble executors into a plugin manifest with definePlugin:
import { definePlugin } from '@colony/sdk';import { draftExecutor } from './draft.js';import { analyzeExecutor } from './analyze.js';
export default definePlugin({ name: 'colony-content', version: '0.1.0', executors: { analyze: analyzeExecutor, draft: draftExecutor, },});| Field | Type | Description |
|---|---|---|
name | string | Plugin name; must match the name in colony.config.yaml’s registration entry |
version | string | Semver-style version string |
executors | Record<string, ExecutorDefinition> | At least one executor is required |
The default export of the plugin’s entry module must be the return value of definePlugin(...). Any other export shape causes the plugin loader to reject the module at startup.
PluginManifest interface
Section titled “PluginManifest interface”interface PluginManifest { readonly name: string; readonly version: string; readonly executors: Readonly<Record<string, ExecutorDefinition<readonly string[]>>>;}Executor types
Section titled “Executor types”The run function receives two arguments:
ExecutorContext
Section titled “ExecutorContext”| Field | Type | Description |
|---|---|---|
repo | RepoInfo | Repository owner, name, default branch, clone URL |
issue | IssueInfo | Issue number, title, body, labels, author |
task | TaskInfo | Task id, type, attempt number, metadata, inputs |
workspace | WorkspaceInfo | Local path and branch name of the git worktree |
vcs | VcsHandle | VCS operations: createPullRequest, addComment, addLabels |
log | Logger | Pino-compatible structured logger |
reportCost | (event: CostEvent) => void | Report token/cost usage to Colony’s cost tracker |
config | ExecutorConfig | Config passed through from colony.config.yaml (includes review sub-config) |
TaskInfo
Section titled “TaskInfo”| Field | Type | Description |
|---|---|---|
id | string | Unique task identifier |
taskType | string | Task type string (e.g. develop, review) |
attemptNumber | number | Retry attempt number (starts at 1) |
metadata | Record<string, unknown> | Arbitrary metadata attached to the task |
inputs | Record<string, unknown> | undefined | Structured inputs forwarded via workflow on.outputs |
ExecutorOutcome
Section titled “ExecutorOutcome”interface ExecutorOutcome { outcome: string; // one of the outcomes declared in defineExecutor metadata?: Record<string, unknown>;}To request a retry, return { outcome: 'retry', metadata: { retry_after_ms: 30000, reason: 'CI not settled' } } — note that retry must not be declared in outcomes because it is a reserved framework outcome.
Registering a plugin
Section titled “Registering a plugin”Plugins are registered in colony.config.yaml under the plugins.registered block:
plugins: registered: - name: colony-content module: ./node_modules/@colony/plugins-colony-content/dist/index.js tenants: - '*'| Field | Required | Description |
|---|---|---|
name | yes | Must match the plugin manifest’s name field |
module | yes | Path to the plugin’s entry module, resolved relative to colony.config.yaml |
tenants | no | Array of tenant external-IDs allowed to use this plugin; '*' matches any tenant. Defaults to ['*'] |
The tenants field lets you deploy one worker with multiple plugins while restricting each to specific repos.
Plugin loader behaviour
Section titled “Plugin loader behaviour”The worker’s plugin loader runs at startup, before the first task is claimed:
- Fail-fast — if any registered plugin fails to load, the worker exits rather than starting with a partial registry.
- Module import — the loader resolves
modulerelative to the config file directory and dynamic-imports the module. - Default export validation — the default export must be a
definePlugin(...)manifest object. - Name match —
manifest.namemust exactly match the config entry’sname; a mismatch throwsPluginLoadError. - Executor-ref resolution — workflow references of the form
plugin:<name>/<key>are resolved by matching<name>tomanifest.nameand<key>to a key inmanifest.executors. Referencing a key that doesn’t exist in the manifest throws at resolve time.
Worked example: colony-content plugin
Section titled “Worked example: colony-content plugin”The packages/sdk/examples/plugin-content-stub/ directory contains a fully-wired stub demonstrating the plugin structure. Both executors return noop (a Stage-0 stub), so this example illustrates plugin authoring patterns rather than a production content pipeline.
Plugin executors (src/index.ts)
Section titled “Plugin executors (src/index.ts)”import { definePlugin } from '@colony/sdk';import { draftExecutor } from './draft.js';import { analyzeExecutor } from './analyze.js';
export default definePlugin({ name: 'colony-content', version: '0.1.0', executors: { analyze: analyzeExecutor, draft: draftExecutor, },});The analyze executor declares outcomes success, needs_clarification, no_work_pr_open, no_work_done, failure. The draft executor declares draft_created, needs_clarification, failure. Neither declares noop, retry, or error.
Workflow (workflow/colony-content.yaml)
Section titled “Workflow (workflow/colony-content.yaml)”The companion workflow at workflow/colony-content.yaml references these executors as plugin:colony-content/analyze and plugin:colony-content/draft. Every outcome declared by each executor appears in the workflow’s on: block for the states that use it. See the Workflows page for the full workflow YAML.
Config registration
Section titled “Config registration”plugins: registered: - name: colony-content module: ./node_modules/@colony/plugins-colony-content/dist/index.jsValidation
Section titled “Validation”After creating a workflow YAML that references your plugin’s executors, validate the schema:
colony workflow validate ./workflow/colony-content.yamlThis confirms the YAML schema is valid and all state-target cross-references resolve. The outcome-coverage check (every declared executor outcome is handled in on:) runs when the worker loads the workflow at startup.