|
| 1 | +# opencode-workflows |
| 2 | + |
| 3 | +Workflow automation plugin for OpenCode using the Mastra workflow engine. Define deterministic, multi-step processes that agents can trigger to perform complex tasks reliably. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Deterministic Automation**: Define rigid, multi-step processes (DAGs) in JSON |
| 8 | +- **Agentic Triggering**: Agents can call workflows as tools |
| 9 | +- **Hybrid Execution**: Mix shell commands, API calls, and LLM prompts |
| 10 | +- **Human-in-the-Loop**: Suspend workflows for human approval |
| 11 | +- **Parallel Execution**: Run independent steps concurrently |
| 12 | + |
| 13 | +## Installation |
| 14 | + |
| 15 | +```bash |
| 16 | +npm install opencode-workflows |
| 17 | +``` |
| 18 | + |
| 19 | +## Configuration |
| 20 | + |
| 21 | +Add to your `opencode.json`: |
| 22 | + |
| 23 | +```json |
| 24 | +{ |
| 25 | + "plugin": ["opencode-workflows"] |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +### Plugin Options |
| 30 | + |
| 31 | +```json |
| 32 | +{ |
| 33 | + "plugin": [ |
| 34 | + ["opencode-workflows", { |
| 35 | + "workflowDirs": [".opencode/workflows"], |
| 36 | + "dbPath": ".opencode/data/workflows.db", |
| 37 | + "verbose": false |
| 38 | + }] |
| 39 | + ] |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +| Option | Type | Default | Description | |
| 44 | +|--------|------|---------|-------------| |
| 45 | +| `workflowDirs` | `string[]` | `[".opencode/workflows"]` | Directories to scan for workflow JSON files | |
| 46 | +| `dbPath` | `string` | `".opencode/data/workflows.db"` | Path to SQLite database for persisting workflow runs | |
| 47 | +| `verbose` | `boolean` | `false` | Enable verbose logging | |
| 48 | + |
| 49 | +### Persistence |
| 50 | + |
| 51 | +Workflow runs are automatically persisted to a LibSQL (SQLite) database. This enables: |
| 52 | + |
| 53 | +- **Crash Recovery**: Active runs are restored on plugin restart |
| 54 | +- **Run History**: Query past workflow executions via `/workflow runs` |
| 55 | +- **Suspend/Resume**: Suspended workflows survive session restarts |
| 56 | + |
| 57 | +The database is created automatically at the configured `dbPath`. |
| 58 | + |
| 59 | +## Workflow Definitions |
| 60 | + |
| 61 | +Create workflow definitions in `.opencode/workflows/` as JSON files: |
| 62 | + |
| 63 | +```json |
| 64 | +{ |
| 65 | + "id": "deploy-prod", |
| 66 | + "description": "Deploys the application to production", |
| 67 | + "inputs": { |
| 68 | + "version": "string" |
| 69 | + }, |
| 70 | + "steps": [ |
| 71 | + { |
| 72 | + "id": "check-git", |
| 73 | + "type": "shell", |
| 74 | + "command": "git status --porcelain", |
| 75 | + "description": "Ensure git is clean" |
| 76 | + }, |
| 77 | + { |
| 78 | + "id": "run-tests", |
| 79 | + "type": "shell", |
| 80 | + "command": "npm test", |
| 81 | + "after": ["check-git"] |
| 82 | + }, |
| 83 | + { |
| 84 | + "id": "ask-approval", |
| 85 | + "type": "suspend", |
| 86 | + "description": "Wait for user to approve deployment", |
| 87 | + "after": ["run-tests"] |
| 88 | + }, |
| 89 | + { |
| 90 | + "id": "deploy-script", |
| 91 | + "type": "shell", |
| 92 | + "command": "npm run deploy -- --tag {{inputs.version}}", |
| 93 | + "after": ["ask-approval"] |
| 94 | + } |
| 95 | + ] |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +## Step Types |
| 100 | + |
| 101 | +### Shell Step |
| 102 | +Execute shell commands: |
| 103 | +```json |
| 104 | +{ |
| 105 | + "id": "build", |
| 106 | + "type": "shell", |
| 107 | + "command": "npm run build", |
| 108 | + "cwd": "./packages/app", |
| 109 | + "env": { "NODE_ENV": "production" }, |
| 110 | + "failOnError": true |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +### Tool Step |
| 115 | +Invoke OpenCode tools: |
| 116 | +```json |
| 117 | +{ |
| 118 | + "id": "send-notification", |
| 119 | + "type": "tool", |
| 120 | + "tool": "slack_send", |
| 121 | + "args": { |
| 122 | + "channel": "#releases", |
| 123 | + "text": "Deployed {{inputs.version}}" |
| 124 | + } |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +### Agent Step |
| 129 | +Prompt an LLM: |
| 130 | +```json |
| 131 | +{ |
| 132 | + "id": "generate-changelog", |
| 133 | + "type": "agent", |
| 134 | + "prompt": "Generate a changelog for version {{inputs.version}}", |
| 135 | + "model": "gpt-4", |
| 136 | + "maxTokens": 1000 |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +### Suspend Step |
| 141 | +Pause for human input: |
| 142 | +```json |
| 143 | +{ |
| 144 | + "id": "approval", |
| 145 | + "type": "suspend", |
| 146 | + "message": "Ready to deploy. Resume to continue.", |
| 147 | + "description": "Wait for deployment approval" |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +### HTTP Step |
| 152 | +Make HTTP requests: |
| 153 | +```json |
| 154 | +{ |
| 155 | + "id": "notify-slack", |
| 156 | + "type": "http", |
| 157 | + "method": "POST", |
| 158 | + "url": "https://hooks.slack.com/services/xxx", |
| 159 | + "headers": { |
| 160 | + "Content-Type": "application/json" |
| 161 | + }, |
| 162 | + "body": { |
| 163 | + "text": "Deployed {{inputs.version}}" |
| 164 | + }, |
| 165 | + "failOnError": true |
| 166 | +} |
| 167 | +``` |
| 168 | + |
| 169 | +HTTP step output includes: |
| 170 | +- `body` - Parsed JSON response, or `null` if response is not valid JSON |
| 171 | +- `text` - Raw response text (useful for non-JSON responses or debugging) |
| 172 | +- `status` - HTTP status code |
| 173 | +- `headers` - Response headers |
| 174 | + |
| 175 | +### File Step |
| 176 | +Read, write, or delete files: |
| 177 | +```json |
| 178 | +{ |
| 179 | + "id": "write-version", |
| 180 | + "type": "file", |
| 181 | + "action": "write", |
| 182 | + "path": "./version.txt", |
| 183 | + "content": "{{inputs.version}}" |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +```json |
| 188 | +{ |
| 189 | + "id": "read-config", |
| 190 | + "type": "file", |
| 191 | + "action": "read", |
| 192 | + "path": "./config.json" |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | +## Commands |
| 197 | + |
| 198 | +Use the `/workflow` command: |
| 199 | + |
| 200 | +- `/workflow list` - List available workflows |
| 201 | +- `/workflow show <id>` - Show workflow details |
| 202 | +- `/workflow run <id> [param=value ...]` - Run a workflow |
| 203 | +- `/workflow status <runId>` - Check run status |
| 204 | +- `/workflow resume <runId> [data]` - Resume a suspended workflow |
| 205 | +- `/workflow cancel <runId>` - Cancel a running workflow |
| 206 | +- `/workflow runs [workflowId]` - List recent runs |
| 207 | + |
| 208 | +### Parameter Type Inference |
| 209 | + |
| 210 | +When passing parameters via `/workflow run`, values are automatically converted to their appropriate types: |
| 211 | + |
| 212 | +| Input | Parsed As | |
| 213 | +|-------|-----------| |
| 214 | +| `count=5` | `number` (5) | |
| 215 | +| `ratio=3.14` | `number` (3.14) | |
| 216 | +| `enabled=true` | `boolean` (true) | |
| 217 | +| `debug=false` | `boolean` (false) | |
| 218 | +| `name=hello` | `string` ("hello") | |
| 219 | +| `url=http://example.com?foo=bar` | `string` (preserved) | |
| 220 | + |
| 221 | +This ensures workflow inputs match their expected schema types without manual conversion. |
| 222 | + |
| 223 | +## Agent Tool |
| 224 | + |
| 225 | +Agents can trigger workflows using the `workflow` tool: |
| 226 | + |
| 227 | +```typescript |
| 228 | +// List workflows |
| 229 | +workflow({ mode: "list" }) |
| 230 | + |
| 231 | +// Run a workflow |
| 232 | +workflow({ |
| 233 | + mode: "run", |
| 234 | + workflowId: "deploy-prod", |
| 235 | + params: { version: "1.2.0" } |
| 236 | +}) |
| 237 | + |
| 238 | +// Check status |
| 239 | +workflow({ mode: "status", runId: "abc-123" }) |
| 240 | + |
| 241 | +// Resume suspended workflow |
| 242 | +workflow({ |
| 243 | + mode: "resume", |
| 244 | + runId: "abc-123", |
| 245 | + resumeData: { approved: true } |
| 246 | +}) |
| 247 | +``` |
| 248 | + |
| 249 | +## Template Interpolation |
| 250 | + |
| 251 | +Use `{{expression}}` syntax to reference: |
| 252 | +- `{{inputs.paramName}}` - Workflow input parameters |
| 253 | +- `{{steps.stepId.stdout}}` - Shell step stdout |
| 254 | +- `{{steps.stepId.response}}` - Agent step response |
| 255 | +- `{{steps.stepId.result}}` - Tool step result |
| 256 | +- `{{steps.stepId.body}}` - HTTP step response body (parsed JSON or null) |
| 257 | +- `{{steps.stepId.text}}` - HTTP step raw response text |
| 258 | +- `{{steps.stepId.content}}` - File step content (read action) |
| 259 | +- `{{env.VAR_NAME}}` - Environment variables |
| 260 | +- `{{run.id}}` - Current workflow run ID |
| 261 | +- `{{run.workflowId}}` - Workflow definition ID |
| 262 | +- `{{run.startedAt}}` - ISO timestamp when run started |
| 263 | + |
| 264 | +### Type Preservation |
| 265 | + |
| 266 | +When a template contains only a single variable reference (e.g., `"{{inputs.count}}"`), the original type is preserved. This means: |
| 267 | +- `"{{inputs.count}}"` with `count=42` returns the number `42`, not the string `"42"` |
| 268 | +- `"Count: {{inputs.count}}"` returns `"Count: 42"` (string interpolation) |
| 269 | + |
| 270 | +### Conditional Execution |
| 271 | + |
| 272 | +Steps can include a `condition` to control execution: |
| 273 | +```json |
| 274 | +{ |
| 275 | + "id": "deploy-prod", |
| 276 | + "type": "shell", |
| 277 | + "command": "deploy.sh", |
| 278 | + "condition": "{{inputs.environment}}" |
| 279 | +} |
| 280 | +``` |
| 281 | +The step is skipped if the condition evaluates to `"false"`, `"0"`, or `""`. |
| 282 | + |
| 283 | +## Dependencies |
| 284 | + |
| 285 | +Steps can declare dependencies using `after`: |
| 286 | + |
| 287 | +```json |
| 288 | +{ |
| 289 | + "id": "deploy", |
| 290 | + "type": "shell", |
| 291 | + "command": "deploy.sh", |
| 292 | + "after": ["build", "test"] |
| 293 | +} |
| 294 | +``` |
| 295 | + |
| 296 | +Steps at the same dependency level run in parallel. |
| 297 | + |
| 298 | +## License |
| 299 | + |
| 300 | +MIT |
0 commit comments