docs/design/plugin-system.md
Status: Design proposal -- not yet implemented
Design document for the Gas Town plugin system. Written 2026-01-11, crew/george session.
Gas Town needs extensible, project-specific automation that runs during Deacon patrol cycles. The immediate use case is rebuilding stale binaries (gt, bd, wv), but the pattern generalizes to any periodic maintenance task.
Current state:
~/gt/plugins/ directory exists with READMEReality is truth. State is derived.
Plugin state (last run, run count, results) lives on the ledger as wisps, not in shadow state files. Gate evaluation queries the ledger directly.
Agent decides. Go transports.
The Deacon (agent) evaluates gates and decides whether to dispatch. Go code provides transport (gt dog dispatch) but doesn't make decisions.
| Layer | Plugin Analog |
|---|---|
| Molecule | plugin.md - work template with TOML frontmatter |
| Ephemeral | Plugin-run wisps - high-volume, digestible |
| Observable | Plugin runs appear in bd activity feed |
| Workflow | Gate → Dispatch → Execute → Record → Digest |
~/gt/
├── plugins/ # Town-level plugins (universal)
│ └── README.md
├── gastown/
│ └── plugins/ # Rig-level plugins
│ └── rebuild-gt/
│ └── plugin.md
├── beads/
│ └── plugins/
│ └── rebuild-bd/
│ └── plugin.md
└── wyvern/
└── plugins/
└── rebuild-wv/
└── plugin.md
Town-level (~/gt/plugins/): Universal plugins that apply everywhere.
Rig-level (<rig>/plugins/): Project-specific plugins.
The Deacon scans both locations during patrol.
Key insight: Plugin execution should not block Deacon patrol.
Dogs are reusable workers designed for infrastructure tasks. Plugin execution is dispatched to dogs:
Deacon Patrol Dog Worker
───────────────── ─────────────────
1. Scan plugins
2. Evaluate gates
3. For open gates:
└─ gt dog dispatch plugin ──→ 4. Execute plugin
(non-blocking) 5. Create result wisp
6. Send DOG_DONE
4. Continue patrol
...
5. Process DOG_DONE ←── (next cycle)
Benefits:
Each plugin run creates a wisp:
bd create --wisp-type patrol \
--labels type:plugin-run,plugin:rebuild-gt,rig:gastown,result:success \
--description "Rebuilt gt: abc123 → def456 (5 commits)" \
"Plugin: rebuild-gt [success]"
Gate evaluation queries wisps instead of state files:
# Cooldown check: any runs in last hour?
bd list --wisp-type patrol --label plugin:rebuild-gt --created-after 1h -n 1
Derived state (no state.json needed):
| Query | Command |
|---|---|
| Last run time | bd list --label=plugin:X --limit=1 --json |
| Run count | bd list --label=plugin:X --json | jq length |
| Last result | Parse result: label from latest wisp |
| Failure rate | Count result:failure vs total |
Like cost digests, plugin wisps accumulate and get squashed daily:
gt plugin digest --yesterday
Creates: Plugin Digest 2026-01-10 bead with summary
Deletes: Individual plugin-run wisps from that day
This keeps the ledger clean while preserving audit history.
rebuild-gt/
└── plugin.md # Definition with TOML frontmatter
+++
name = "rebuild-gt"
description = "Rebuild stale gt binary from source"
version = 1
[gate]
type = "cooldown"
duration = "1h"
[tracking]
labels = ["plugin:rebuild-gt", "rig:gastown", "category:maintenance"]
digest = true
[execution]
timeout = "5m"
notify_on_failure = true
+++
# Rebuild gt Binary
Instructions for the dog worker to execute...
# Required
name = "string" # Unique plugin identifier
description = "string" # Human-readable description
version = 1 # Schema version (for future evolution)
[gate]
type = "cooldown|cron|condition|event|manual"
# Type-specific fields:
duration = "1h" # For cooldown
schedule = "0 9 * * *" # For cron
check = "gt stale -q" # For condition (exit 0 = run)
on = "startup" # For event
[tracking]
labels = ["label:value", ...] # Labels for execution wisps
digest = true|false # Include in daily digest
[execution]
timeout = "5m" # Max execution time
notify_on_failure = true # Escalate on failure
severity = "low" # Escalation severity if failed
| Type | Config | Behavior |
|---|---|---|
cooldown | duration = "1h" | Query wisps, run if none in window |
cron | schedule = "0 9 * * *" | Run on cron schedule |
condition | check = "cmd" | Run check command, run if exit 0 |
event | on = "startup" | Run on Deacon startup |
manual | (no gate section) | Never auto-run, dispatch explicitly |
The markdown body after the frontmatter contains agent-executable instructions. The dog worker reads and executes these steps.
Standard sections:
gt stale -- Expose binary staleness check (human-readable, --json, --quiet exit code)gt dog dispatch --plugin <name> -- Dispatch plugin execution to an idle dog (non-blocking)gt plugin list|show|run|digest|history -- Plugin management and execution historygt stale command - Expose CheckStaleBinary() via CLIgt dog dispatch --plugin - Formalized dog dispatchgt escalate command - Unified escalation APIrebuild-gt plugin - The actual gastown pluginPlugin discovery in multiple clones: If gastown has crew/george, crew/max, crew/joe - which clone's plugins/ dir is canonical? Probably: scan all, dedupe by name, prefer rig-root if exists.
Dog assignment: Should specific plugins prefer specific dogs? Or any idle dog?
Plugin dependencies: Can plugins depend on other plugins? Probably not in v1.
Plugin disable/enable: How to temporarily disable a plugin without deleting it? Label on a plugin bead? enabled = false in frontmatter?