A minimal implementation of enforced workflows for OpenCode agents.
Core idea: Force AI to follow predefined steps instead of improvising.
# .opencode/abilities/deploy.yaml
name: deploy
description: Deploy with tests
inputs:
version:
type: string
required: true
steps:
- id: test
type: script
run: npm test
validation:
exit_code: 0
- id: deploy
type: script
needs: [test]
run: ./deploy.sh {{inputs.version}}
ability.run({ name: "deploy", inputs: { version: "v1.0.0" } })
npm test./deploy.sh v1.0.0ability.listList all loaded abilities
ability.list()
// Returns: "- test: Minimal test ability (3 steps)"
ability.runExecute an ability
ability.run({
name: "test",
inputs: { message: "Hello" }
})
ability.statusCheck current execution
ability.status()
// Returns: { status: "running", currentStep: "step2", progress: "1/3" }
ability.cancelStop active execution
ability.cancel()
// Returns: { status: "cancelled" }
Runs shell commands
- id: build
type: script
run: npm run build
validation:
exit_code: 0
Features:
{{inputs.version}}needs: [test]When a script step is running, all tools are blocked except:
ability.listability.statusability.cancelread, glob, grep (read-only)Example:
ability.run({ name: "test" })
[while running]
bash({ command: "ls" }) // ❌ BLOCKED
Every chat message shows ability status:
🔄 Active Ability: test
Progress: 1/3 steps completed
Current Step: step2
⚠️ ENFORCEMENT ACTIVE
.opencode/
abilities/
test.yaml # Your ability
deploy.yaml # Another ability
name: string # Required: unique name
description: string # Required: what it does
inputs: # Optional: input parameters
param_name:
type: string|number|boolean
required: boolean
default: any
steps: # Required: at least 1 step
- id: string # Required: unique step ID
type: script # Required: only 'script' in minimal version
description: string # Optional: what this step does
run: string # Required: shell command
needs: [step_ids] # Optional: dependencies
validation: # Optional: validation rules
exit_code: number # Expected exit code (default: any)
Use {{inputs.name}} in commands:
inputs:
version:
type: string
required: true
steps:
- id: deploy
run: ./deploy.sh {{inputs.version}}
Use needs to order steps:
steps:
- id: test
run: npm test
- id: build
needs: [test] # Runs AFTER test
run: npm run build
- id: deploy
needs: [build] # Runs AFTER build
run: ./deploy.sh
inputs:
count:
type: number
required: true
If missing: ❌ Input validation failed: count is required
steps:
- id: test
run: npm test
validation:
exit_code: 0
If fails: ❌ Exit code 1, expected 0
cd packages/plugin-abilities
bun test-minimal.ts
# .opencode/abilities/hello.yaml
name: hello
description: Test ability
steps:
- id: greet
type: script
run: echo "Hello World"
ability.run({ name: "hello" })
// Output: ✅ greet: completed
// Output: Hello World
Not Included:
These will be added after core is validated.
.opencode/abilities/ directory existsopencode.jsonability.cancel() to stopability.cancel() to stop current executionOnce minimal version is validated:
User Request
↓
ability.run tool
↓
ExecutionManager.execute()
↓
For each step:
├─ Set as currentStep
├─ Block other tools (tool.execute.before hook)
├─ Inject context (chat.message hook)
├─ Execute script
├─ Validate result
└─ Continue or fail
↓
Return execution result
src/opencode-plugin.ts - Plugin hooks and toolssrc/executor/index.ts - Script executionsrc/executor/execution-manager.ts - State trackingsrc/types/index.ts - TypeScript typesexamples/test/ability.yaml - Example abilitySee MINIMAL_TEST.md for detailed testing guide.
See SIMPLIFICATION_SUMMARY.md for what changed.