ZERG Plugin System
Extend ZERG with custom quality gates, lifecycle hooks, and worker launchers.
Overview
The ZERG plugin system provides three extension points:
- Quality Gate Plugins — Custom validation after merges (lint, security scans, benchmarks)
- Lifecycle Hook Plugins — React to events (task starts/completes, level finishes, merges)
- Launcher Plugins — Custom worker execution environments (Kubernetes, SSH clusters, cloud VMs)
All plugins are additive only — they cannot mutate orchestrator state, only observe and react. Plugin failures never crash the orchestrator; they are logged and execution continues.
Plugin Types
QualityGatePlugin
Validates code quality after each level merge. Runs sequentially after built-in gates.
Abstract Base Class (zerg/plugins.py:51-62):
from zerg.plugins import QualityGatePlugin, GateContext
from zerg.types import GateRunResult
from zerg.constants import GateResult
class MyCustomGate(QualityGatePlugin):
@property
def name(self) -> str:
return "my-custom-gate"
def run(self, ctx: GateContext) -> GateRunResult:
# ctx.feature: str — feature name
# ctx.level: int — level just merged
# ctx.cwd: Path — working directory
# ctx.config: ZergConfig — full config
# Run validation logic...
return GateRunResult(
gate_name=self.name,
result=GateResult.PASS, # PASS | FAIL | SKIP | TIMEOUT | ERROR
command="my-custom-check",
exit_code=0,
stdout="All checks passed",
stderr="",
)
Execution flow: Built-in gates (lint, build, test) → Plugin gates (registered order) → Merge completes
Security:
- Read-only access to config and filesystem
- 5-minute timeout enforced (configurable via YAML)
- Exceptions caught and logged as ERROR result
LifecycleHookPlugin
Observes ZERG lifecycle events without blocking execution.
Abstract Base Class (zerg/plugins.py:64-74):
from zerg.plugins import LifecycleHookPlugin, LifecycleEvent
class MyNotificationHook(LifecycleHookPlugin):
@property
def name(self) -> str:
return "slack-notifier"
def on_event(self, event: LifecycleEvent) -> None:
# event.event_type: str — from PluginHookEvent enum
# event.data: dict — event-specific payload
# event.timestamp: datetime — when event occurred
if event.event_type == "task_completed":
task_id = event.data["task_id"]
# Send Slack notification...
Available events (from zerg/constants.py:143-149):
| Event Type | Emitted When | Data Payload |
|---|---|---|
task_started |
Worker begins task execution | task_id, worker_id, level |
task_completed |
Task verification passes | task_id, worker_id, duration, output |
level_complete |
All tasks in level finish | level, task_count, elapsed_time |
merge_complete |
Level branches merged | level, branch_count, conflicts |
worker_spawned |
New worker starts | worker_id, port, mode |
quality_gate_run |
Gate executes | gate_name, result, level |
rush_started |
/zerg:rush begins |
feature, workers, config |
rush_finished |
All levels complete | feature, total_time, tasks_completed |
Security:
- Hooks never block execution — failures are logged
- Per-hook try/except isolation
- No access to mutable orchestrator state
LauncherPlugin
Provides custom worker execution environments beyond subprocess and container.
Abstract Base Class (zerg/plugins.py:77-87):
from zerg.plugins import LauncherPlugin
from zerg.launcher import WorkerLauncher
class K8sLauncherPlugin(LauncherPlugin):
@property
def name(self) -> str:
return "kubernetes"
def create_launcher(self, config: Any) -> WorkerLauncher:
# Return a WorkerLauncher subclass instance
return K8sWorkerLauncher(config)
Execution flow: Check launcher plugins → Fallback to built-in (subprocess | container)
Security:
- Launchers inherit from
WorkerLauncherABC - Must implement
launch(),wait(),cleanup()methods - No direct orchestrator access
Configuration
YAML Hooks (Simple)
For simple shell commands triggered by lifecycle events:
# .zerg/config.yaml
plugins:
enabled: true
hooks:
- event: task_completed
command: echo "Task completed at $(date)"
timeout: 60
- event: level_complete
command: ./scripts/notify-slack.sh "Level {level} done"
timeout: 120
- event: merge_complete
command: |
python scripts/generate_report.py \
--level {level} \
--feature {feature}
timeout: 180
Fields:
event— FromPluginHookEventenum (see lifecycle event table)command— Shell command (parsed withshlex.split, no shell=True)timeout— Max execution time in seconds (1-600, default: 60)
Variable substitution: {level}, {feature}, {task_id}, {worker_id} — replaced from event data
Execution: Commands run in subprocess with no shell, isolated per-hook with try/except
YAML Gates (Simple)
For custom quality gates via shell commands:
plugins:
quality_gates:
- name: security-scan
command: bandit -r src/ --severity medium
required: false
timeout: 300
- name: complexity-check
command: radon cc src/ --min B
required: true
timeout: 120
Fields:
name— Unique gate identifiercommand— Shell command to executerequired— Iftrue, gate failure blocks merge (default:false)timeout— Max execution time in seconds (1-3600, default: 300)
Python Entry Points (Advanced)
For complex plugins with custom logic, authentication, or API calls — see plugins.details.md for full examples.
TaskCreate/TaskUpdate Integration
All plugin management commands must integrate with the Claude Code Task system per ZERG conventions.
Creating Plugin-Related Tasks
When registering or managing plugins:
# Create task for plugin operations
task_id = TaskCreate(
subject="[Plugins] Register custom quality gates",
status="pending",
metadata={"feature": feature_name, "command": "zerg:plugins"}
)
# Mark in progress
TaskUpdate(task_id=task_id, status="in_progress")
# On completion
TaskUpdate(task_id=task_id, status="completed")
Task Subject Conventions
Use bracketed prefixes for plugin-related tasks:
[Plugins] Register {name}— Plugin registration[Gate] Run {gate_name}— Quality gate execution[Hook] Process {event_type}— Lifecycle hook invocation[Launcher] Spawn {worker_id}— Custom launcher execution
State Tracking
Plugin operations should write state to both:
- Task system (authoritative) — via
TaskCreate/TaskUpdate; read viaTaskList/TaskGet - State JSON (supplementary) —
.zerg/state/plugins.json
If Task system and state JSON disagree, Task system wins (verify via TaskList).
Help
When --help is passed in $ARGUMENTS, display usage and exit:
/zerg:plugins — Extend ZERG with custom quality gates, lifecycle hooks, and worker launchers.
Flags:
--help Show this help message