Plugins
Plugins package governed hook integrations without forking core config. A plugin is a self-contained bundle with:
- an
agentxchain-plugin.jsonmanifest - hook executables or HTTP endpoints
- optional plugin configuration validated at install and upgrade time
Installed plugins are copied into .agentxchain/plugins/, merged into the governed hook config, and then executed by the same hook runner and tamper protections as operator-defined hooks.
Plugin manifest
Every plugin contains an agentxchain-plugin.json manifest at its root:
{
"schema_version": "0.1",
"name": "@agentxchain/plugin-slack-notify",
"version": "0.1.0",
"description": "Posts governed lifecycle notifications to Slack.",
"hooks": {
"after_acceptance": [
{
"name": "slack_notify_acceptance",
"type": "process",
"command": ["node", "./hooks/after-acceptance.js"],
"timeout_ms": 5000,
"mode": "advisory"
}
],
"before_gate": [
{
"name": "slack_notify_gate",
"type": "process",
"command": ["node", "./hooks/before-gate.js"],
"timeout_ms": 5000,
"mode": "advisory"
}
]
},
"config_schema": {
"type": "object",
"properties": {
"webhook_env": {
"type": "string",
"default": "AGENTXCHAIN_SLACK_WEBHOOK_URL"
},
"mention": {
"type": "string"
}
}
}
}
Manifest fields
| Field | Type | Required | Description |
|---|---|---|---|
schema_version | string | Yes | Must be "0.1" |
name | string | Yes | Package-style identifier. Scoped names such as @agentxchain/plugin-slack-notify are supported |
version | string | Yes | Semver version string |
description | string | No | Short description shown in plugin list output |
hooks | object | Yes | Map of hook phase to hook definition array |
config_schema | object | No | JSON Schema used to validate plugin config during install and upgrade |
Hook phases
Plugins register against hook phases, not free-form lifecycle event names:
| Phase | When it fires | Blocking allowed |
|---|---|---|
before_assignment | Before a turn is assigned to a role | Yes |
after_dispatch | After a turn is dispatched to an adapter | Yes |
before_validation | Before a result is validated | Yes |
after_validation | After validation completes | Yes |
before_acceptance | Before a turn is accepted | Yes |
after_acceptance | After acceptance commits | No |
before_gate | Before human gate approval executes | Yes |
on_escalation | When the run escalates | No |
Hook definition fields
All hooks require:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique hook name within its phase. Must match ^[a-z0-9_-]+$ |
type | string | Yes | process or http |
timeout_ms | integer | Yes | Timeout in milliseconds, between 100 and 30000 |
mode | string | Yes | blocking or advisory. after_acceptance and on_escalation cannot be blocking |
env | object | No | Extra environment variables injected into the hook |
process hooks also require command, which must be a non-empty argv array. Relative command tokens such as ./hooks/after-acceptance.js are rewritten during install so they still resolve from the governed project root.
http hooks also require:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTP or HTTPS endpoint |
method | string | Yes | Must be POST |
headers | object | No | Header values support ${VAR} interpolation from the process environment plus hook.env |
Installing plugins
Built-in plugins (recommended)
AgentXchain ships with built-in plugins that can be installed by short name:
agentxchain plugin install slack-notify
agentxchain plugin install json-report
agentxchain plugin install github-issues
To see all available built-in plugins:
agentxchain plugin list-available
Other sources
Plugins can also be installed from local directories, archives, or npm packages:
# Local directory
agentxchain plugin install ./path/to/my-plugin
# Archive (.tgz or .tar.gz)
agentxchain plugin install ./downloads/plugin-slack-notify-0.1.0.tar.gz
# npm package
agentxchain plugin install @agentxchain/plugin-slack-notify
The CLI resolves built-in short names first, then local directories, then falls back to npm pack <spec> for tarball-based install.
Install flags
| Flag | Description |
|---|---|
--config <json> | Inline JSON plugin config validated against config_schema |
--config-file <path> | Read plugin config JSON from a file |
-j, --json | Output as JSON |
--config and --config-file are mutually exclusive.
What happens on install
- Manifest validation: the manifest is parsed and validated.
- Config validation: if the plugin declares
config_schema, the supplied config is validated and stored underplugins.<name>.config. - Duplicate and collision protection: if the plugin name is already installed, install fails. If any hook name collides with an existing hook name in the same phase, install also fails. There is no force override for install.
- Command path rewriting: relative process-hook command paths are rewritten to the installed location under
.agentxchain/plugins/.... - Registration: the plugin is recorded in
agentxchain.jsonunderpluginsand its hook definitions are merged into the roothooksconfig.
{
"hooks": {
"after_acceptance": [
{
"name": "slack_notify_acceptance",
"type": "process",
"command": [
"node",
".agentxchain/plugins/agentxchain--plugin-slack-notify--deadbeef/hooks/after-acceptance.js"
],
"timeout_ms": 5000,
"mode": "advisory",
"env": {
"AGENTXCHAIN_PLUGIN_NAME": "@agentxchain/plugin-slack-notify",
"AGENTXCHAIN_PLUGIN_VERSION": "0.1.0",
"AGENTXCHAIN_PLUGIN_CONFIG": "{\"webhook_env\":\"AGENTXCHAIN_SLACK_WEBHOOK_URL\"}"
}
}
]
},
"plugins": {
"@agentxchain/plugin-slack-notify": {
"schema_version": "0.1",
"name": "@agentxchain/plugin-slack-notify",
"version": "0.1.0",
"install_path": ".agentxchain/plugins/agentxchain--plugin-slack-notify--deadbeef",
"source": {
"type": "local_path",
"spec": "./plugins/plugin-slack-notify"
},
"hooks": {
"after_acceptance": ["slack_notify_acceptance"],
"before_gate": ["slack_notify_gate"]
},
"config": {
"webhook_env": "AGENTXCHAIN_SLACK_WEBHOOK_URL"
}
}
}
}
Listing plugins
agentxchain plugin list
| Flag | Description |
|---|---|
-j, --json | Output as JSON |
Text output:
Installed plugins: 1
@agentxchain/[email protected]
Path: .agentxchain/plugins/agentxchain--plugin-slack-notify--deadbeef
Source: local_path (./plugins/plugin-slack-notify)
Present: yes
Hooks: after_acceptance: slack_notify_acceptance | before_gate: slack_notify_gate
With --json:
{
"plugins": [
{
"name": "@agentxchain/plugin-slack-notify",
"version": "0.1.0",
"description": "Posts governed lifecycle notifications to a Slack incoming webhook.",
"install_path": ".agentxchain/plugins/agentxchain--plugin-slack-notify--deadbeef",
"source": {
"type": "local_path",
"spec": "./plugins/plugin-slack-notify"
},
"hooks": {
"after_acceptance": ["slack_notify_acceptance"],
"before_gate": ["slack_notify_gate"],
"on_escalation": ["slack_notify_escalation"]
},
"installed": true
}
]
}
Upgrading plugins
agentxchain plugin upgrade @agentxchain/plugin-slack-notify
The optional [source] positional argument specifies a local directory, archive, or npm package to upgrade from. If omitted, the plugin is upgraded from its recorded original source.
| Flag | Description |
|---|---|
--config <json> | Inline JSON plugin config validated against config_schema |
--config-file <path> | Read plugin config JSON from a file |
-j, --json | Output as JSON |
Upgrade performs an atomic swap:
- The replacement source is resolved and validated.
- Existing plugin hooks are removed from the in-memory config, then the replacement hooks are merged and validated.
- The old install directory is renamed to a rollback path under
.agentxchain/plugins/...rollback-<timestamp>. - The new directory is moved into place and
agentxchain.jsonis committed. - If the config write or swap fails, the previous install is restored.
To upgrade from a specific source:
agentxchain plugin upgrade @agentxchain/plugin-slack-notify ./downloads/plugin-slack-notify-0.2.0.tar.gz
Removing plugins
agentxchain plugin remove @agentxchain/plugin-slack-notify
| Flag | Description |
|---|---|
-j, --json | Output as JSON |
Removal deletes the plugin metadata entry, strips only that plugin's recorded hook names from agentxchain.json, and removes the installed directory from .agentxchain/plugins/.
Authoring a plugin
A minimal process-hook plugin looks like this:
my-plugin/
agentxchain-plugin.json
hooks/
after-acceptance.js
Process hook contract
Process hooks receive the full hook payload as JSON on stdin. The runner also injects environment variables:
| Variable | Description |
|---|---|
AGENTXCHAIN_HOOK_PHASE | Current hook phase |
AGENTXCHAIN_HOOK_NAME | Hook name from the manifest |
AGENTXCHAIN_RUN_ID | Current run ID when available |
AGENTXCHAIN_PROJECT_ROOT | Governed project root |
AGENTXCHAIN_PLUGIN_NAME | Plugin manifest name |
AGENTXCHAIN_PLUGIN_VERSION | Plugin manifest version |
AGENTXCHAIN_PLUGIN_CONFIG | Validated plugin config as a JSON string when config exists |
The hook may print a JSON verdict to stdout:
{
"verdict": "allow",
"message": "Slack notification sent",
"annotations": [
{ "key": "slack_channel", "value": "#ops" }
]
}
Valid verdicts are allow, warn, and block. Advisory hooks that return block are downgraded to a warning by the orchestrator. after_acceptance annotations are written to .agentxchain/hook-annotations.jsonl.
import { readFileSync } from 'node:fs';
const payload = JSON.parse(readFileSync(0, 'utf8'));
const pluginConfig = process.env.AGENTXCHAIN_PLUGIN_CONFIG
? JSON.parse(process.env.AGENTXCHAIN_PLUGIN_CONFIG)
: {};
process.stdout.write(JSON.stringify({
verdict: 'allow',
message: `Handled ${payload.hook_phase} for ${process.env.AGENTXCHAIN_PLUGIN_NAME}`,
annotations: pluginConfig.mention
? [{ key: 'mention', value: pluginConfig.mention }]
: [],
}));
HTTP hooks
HTTP hooks use the same phase system and verdict contract as process hooks. They send the JSON hook envelope as the HTTP request body and may return a JSON verdict in the response body.
Configuration
{
"name": "notify_webhook",
"type": "http",
"url": "https://my-service.example.com/hooks/after-acceptance",
"method": "POST",
"timeout_ms": 15000,
"mode": "advisory",
"env": {
"HOOK_TOKEN": "secret-token"
},
"headers": {
"Authorization": "Bearer ${HOOK_TOKEN}"
}
}
Behavior
- Method:
POSTonly. Other methods are rejected at manifest validation time. - Body: the full hook envelope is sent as JSON in the request body.
- Header interpolation: header values support
${VAR}interpolation from the current process environment plus any strings declared inhook.env. - Response handling: non-2xx responses are treated as hook failures. There is no built-in retry loop.
- Verdict surface: HTTP hooks can return the same verdict JSON as process hooks.
Failure modes
| Failure | Impact | Recovery |
|---|---|---|
| Process hook exits non-zero | Command fails. Blocking phases stop the workflow; advisory phases are logged and surfaced | Fix the hook and rerun the blocked operator step |
| Process or HTTP hook times out | Hook is killed with SIGTERM and the step fails or warns based on phase and mode | Increase timeout_ms or shorten the hook |
| HTTP hook returns non-2xx | Treated as a hook failure | Fix the endpoint or credentials |
| Manifest validation fails on install | Install is rejected | Fix the manifest and retry |
| Config schema validation fails | Install or upgrade is rejected | Provide config that satisfies config_schema |
| Duplicate plugin name | Install is rejected | Remove or upgrade the existing plugin |
| Duplicate hook name in the same phase | Install or upgrade is rejected with no partial config mutation | Rename the hook or remove the conflict |
| Partial install failure after copy but before config write | Staged plugin files are deleted and config remains unchanged | Fix the failing hook path or config issue and retry |
| Upgrade commit failure | Previous install directory and config are restored | Fix the failure and rerun plugin upgrade |
| Hook tampers with protected orchestrator files | The run fails closed and the protected files are restored | Remove the illegal mutation and rerun the blocked step |
Built-in packages
Dedicated package guides:
@agentxchain/plugin-slack-notify
Posts advisory lifecycle notifications to a Slack incoming webhook.
Hook phases: after_acceptance, before_gate, on_escalation
agentxchain plugin install slack-notify
Config:
{
"plugins": {
"@agentxchain/plugin-slack-notify": {
"config": {
"webhook_env": "AGENTXCHAIN_SLACK_WEBHOOK_URL",
"mention": "@here"
}
}
}
}
If mention is not configured, the plugin falls back to AGENTXCHAIN_SLACK_MENTION at runtime. If webhook_env is not configured, it looks for AGENTXCHAIN_SLACK_WEBHOOK_URL first and then SLACK_WEBHOOK_URL.
@agentxchain/plugin-json-report
Writes structured lifecycle report artifacts into .agentxchain/reports/.
Hook phases: after_acceptance, before_gate, on_escalation
agentxchain plugin install json-report
Config:
{
"plugins": {
"@agentxchain/plugin-json-report": {
"config": {
"report_dir": ".agentxchain/reports"
}
}
}
}
report_dir must stay inside the governed project root. The plugin rejects paths that escape the repo and otherwise writes timestamped reports plus latest.json and latest-<hook_phase>.json into the configured directory.
@agentxchain/plugin-github-issues
Mirrors governed run status into one configured GitHub issue with one plugin-owned comment per run.
Hook phases: after_acceptance, on_escalation
agentxchain plugin install github-issues
Config:
{
"plugins": {
"@agentxchain/plugin-github-issues": {
"config": {
"repo": "owner/name",
"issue_number": 42,
"token_env": "GITHUB_TOKEN",
"label_prefix": "agentxchain"
}
}
}
}
This package intentionally does not close issues or set approval-pending labels. The current hook surface has no post-gate callback, so those states would be guesses rather than governed truth.
The built-in packages live in the repo plugins/ directory and can be installed from a local path or published tarball/package spec.