Skip to content

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.

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' };
},
});
FieldTypeDescription
outcomesreadonly 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:

NameMeaning
noopCompletes the work task without transitioning the issue state
retryRe-enqueues the same task with a delay; issue state is unchanged
errorSynthetic 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.)

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,
},
});
FieldTypeDescription
namestringPlugin name; must match the name in colony.config.yaml’s registration entry
versionstringSemver-style version string
executorsRecord<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.

interface PluginManifest {
readonly name: string;
readonly version: string;
readonly executors: Readonly<Record<string, ExecutorDefinition<readonly string[]>>>;
}

The run function receives two arguments:

FieldTypeDescription
repoRepoInfoRepository owner, name, default branch, clone URL
issueIssueInfoIssue number, title, body, labels, author
taskTaskInfoTask id, type, attempt number, metadata, inputs
workspaceWorkspaceInfoLocal path and branch name of the git worktree
vcsVcsHandleVCS operations: createPullRequest, addComment, addLabels
logLoggerPino-compatible structured logger
reportCost(event: CostEvent) => voidReport token/cost usage to Colony’s cost tracker
configExecutorConfigConfig passed through from colony.config.yaml (includes review sub-config)
FieldTypeDescription
idstringUnique task identifier
taskTypestringTask type string (e.g. develop, review)
attemptNumbernumberRetry attempt number (starts at 1)
metadataRecord<string, unknown>Arbitrary metadata attached to the task
inputsRecord<string, unknown> | undefinedStructured inputs forwarded via workflow on.outputs
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.

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:
- '*'
FieldRequiredDescription
nameyesMust match the plugin manifest’s name field
moduleyesPath to the plugin’s entry module, resolved relative to colony.config.yaml
tenantsnoArray 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.

The worker’s plugin loader runs at startup, before the first task is claimed:

  1. Fail-fast — if any registered plugin fails to load, the worker exits rather than starting with a partial registry.
  2. Module import — the loader resolves module relative to the config file directory and dynamic-imports the module.
  3. Default export validation — the default export must be a definePlugin(...) manifest object.
  4. Name matchmanifest.name must exactly match the config entry’s name; a mismatch throws PluginLoadError.
  5. Executor-ref resolution — workflow references of the form plugin:<name>/<key> are resolved by matching <name> to manifest.name and <key> to a key in manifest.executors. Referencing a key that doesn’t exist in the manifest throws at resolve time.

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.

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.

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.

plugins:
registered:
- name: colony-content
module: ./node_modules/@colony/plugins-colony-content/dist/index.js

After creating a workflow YAML that references your plugin’s executors, validate the schema:

Terminal window
colony workflow validate ./workflow/colony-content.yaml

This 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.