1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
|
# Package: `internal/task`
## Overview
The `task` package defines the core domain model for Claudomator. A **Task** represents a discrete unit of work to be executed by a Claude agent — described by natural-language instructions, optional budget and timeout constraints, retry policy, and scheduling metadata. Tasks are authored as YAML files and progress through a well-defined lifecycle from `PENDING` to a terminal state.
---
## Task Struct
```go
type Task struct {
ID string `yaml:"id"`
ParentTaskID string `yaml:"parent_task_id"`
Name string `yaml:"name"`
Description string `yaml:"description"`
Agent AgentConfig `yaml:"agent"`
Timeout Duration `yaml:"timeout"`
Retry RetryConfig `yaml:"retry"`
Priority Priority `yaml:"priority"`
Tags []string `yaml:"tags"`
DependsOn []string `yaml:"depends_on"`
State State `yaml:"-"`
RejectionComment string `yaml:"-"`
QuestionJSON string `yaml:"-"`
CreatedAt time.Time `yaml:"-"`
UpdatedAt time.Time `yaml:"-"`
}
```
| Field | Type | Purpose | Default / Notes |
|--------------------|---------------|----------------------------------------------------------------------|------------------------------------------|
| `ID` | `string` | Unique identifier (UUID). | Auto-generated by `Parse` if empty. |
| `ParentTaskID` | `string` | ID of the parent task; empty for top-level tasks. | Optional. |
| `Name` | `string` | Human-readable name. **Required.** | Must be non-empty. |
| `Description` | `string` | Longer explanation shown in the UI. | Optional. |
| `Agent` | `AgentConfig` | Configuration passed to the Claude agent executor. | See [AgentConfig](#agentconfig-struct). |
| `Timeout` | `Duration` | Maximum wall-clock time for a single execution attempt. | `0` means no limit. |
| `Retry` | `RetryConfig` | Retry policy applied when a run fails. | See [RetryConfig](#retryconfig-struct). |
| `Priority` | `Priority` | Scheduling priority: `high`, `normal`, or `low`. | Default: `normal`. |
| `Tags` | `[]string` | Arbitrary labels for filtering and grouping. | Optional; defaults to `[]`. |
| `DependsOn` | `[]string` | IDs of tasks that must reach `COMPLETED` before this one is queued. | Optional; defaults to `[]`. |
| `State` | `State` | Current lifecycle state. Not read from YAML (`yaml:"-"`). | Set to `PENDING` by `Parse`. |
| `RejectionComment` | `string` | Free-text comment left by a reviewer when rejecting a task. | Not in YAML; populated by storage layer. |
| `QuestionJSON` | `string` | JSON-encoded question posted by the agent while `BLOCKED`. | Not in YAML; populated by storage layer. |
| `CreatedAt` | `time.Time` | Timestamp set when the task is parsed. | Auto-set by `Parse`. |
| `UpdatedAt` | `time.Time` | Timestamp updated on every state change. | Auto-set by `Parse`; updated by DB. |
Fields tagged `yaml:"-"` are runtime-only and are never parsed from YAML task files.
---
## AgentConfig Struct
```go
type AgentConfig struct {
Type string `yaml:"type"`
Model string `yaml:"model"`
ContextFiles []string `yaml:"context_files"`
Instructions string `yaml:"instructions"`
ProjectDir string `yaml:"project_dir"`
MaxBudgetUSD float64 `yaml:"max_budget_usd"`
PermissionMode string `yaml:"permission_mode"`
AllowedTools []string `yaml:"allowed_tools"`
DisallowedTools []string `yaml:"disallowed_tools"`
SystemPromptAppend string `yaml:"system_prompt_append"`
AdditionalArgs []string `yaml:"additional_args"`
SkipPlanning bool `yaml:"skip_planning"`
}
```
| Field | Type | Purpose |
|----------------------|------------|---------------------------------------------------------------------------------------------------------------------------------|
| `Type` | `string` | Agent type classifier. Cleared on retry by `ResetTaskForRetry` so the task can be re-classified. |
| `Model` | `string` | Claude model ID (e.g. `"claude-sonnet-4-6"`). Cleared on retry alongside `Type`. |
| `ContextFiles` | `[]string` | Paths to files pre-loaded as context for the agent. |
| `Instructions` | `string` | Natural-language task instructions. **Required.** |
| `ProjectDir` | `string` | Working directory the agent operates in. |
| `MaxBudgetUSD` | `float64` | API spend cap in USD. `0` means no limit. Must be ≥ 0. |
| `PermissionMode` | `string` | Claude permission mode. Valid values: `default`, `acceptEdits`, `bypassPermissions`, `plan`, `dontAsk`, `delegate`. |
| `AllowedTools` | `[]string` | Explicit tool whitelist passed to the agent. |
| `DisallowedTools` | `[]string` | Explicit tool blacklist passed to the agent. |
| `SystemPromptAppend` | `string` | Text appended to the Claude system prompt. |
| `AdditionalArgs` | `[]string` | Extra CLI arguments forwarded verbatim to the Claude executable. |
| `SkipPlanning` | `bool` | When `true`, the agent skips the planning phase. |
---
## RetryConfig Struct
```go
type RetryConfig struct {
MaxAttempts int `yaml:"max_attempts"`
Backoff string `yaml:"backoff"` // "linear" or "exponential"
}
```
| Field | Type | Purpose | Default |
|---------------|----------|------------------------------------------------------------|-----------------|
| `MaxAttempts` | `int` | Total number of attempts (first run + retries). Min: 1. | `1` |
| `Backoff` | `string` | Wait strategy between retries: `linear` or `exponential`. | `"exponential"` |
---
## YAML Task File Format
A task file may contain a single task or a batch (see next section). Every supported field is shown below with annotations.
```yaml
# Unique identifier. Omit to have Claudomator generate a UUID.
id: "fix-login-bug"
# Display name. Required.
name: "Fix login redirect bug"
# Optional longer description.
description: "Users are redirected to /home instead of /dashboard after login."
agent:
# Agent type for routing/classification. Optional; auto-assigned if blank.
type: "claude"
# Claude model ID. Optional; auto-assigned if blank.
model: "claude-sonnet-4-6"
# Task instructions passed to the agent. Required.
instructions: |
Fix the post-login redirect in src/auth/login.go so that users are
sent to /dashboard instead of /home. Add a regression test.
# Working directory for the agent process.
project_dir: "/workspace/myapp"
# Files loaded into the agent's context before execution starts.
context_files:
- "src/auth/login.go"
- "docs/adr/0003-auth.md"
# USD spending cap. 0 = no limit.
max_budget_usd: 1.00
# Claude permission mode.
# Values: default | acceptEdits | bypassPermissions | plan | dontAsk | delegate
permission_mode: "acceptEdits"
# Tool whitelist. Empty = all tools allowed.
allowed_tools:
- "Read"
- "Edit"
- "Bash"
# Tool blacklist.
disallowed_tools:
- "WebFetch"
# Appended to the agent's system prompt.
system_prompt_append: "Always write tests before implementation."
# Extra arguments forwarded verbatim to the Claude executable.
additional_args:
- "--verbose"
# Skip the planning phase.
skip_planning: false
# Maximum wall-clock time per attempt. Uses Go duration syntax: "30m", "1h", "45s".
# 0 or omitted means no limit.
timeout: "30m"
retry:
# Total attempts including the first run. Must be >= 1.
max_attempts: 3
# "linear" or "exponential"
backoff: "exponential"
# Scheduling priority: high | normal | low
priority: "normal"
# Arbitrary labels for filtering.
tags:
- "bug"
- "auth"
# IDs of tasks that must reach COMPLETED before this task is queued.
depends_on:
- "setup-test-db"
```
---
## Batch File Format
A batch file wraps multiple task definitions under a `tasks:` key. All top-level task fields are supported per entry.
```yaml
tasks:
- name: "Step 1 — scaffold"
agent:
instructions: "Create the initial project structure."
priority: "high"
- name: "Step 2 — implement"
agent:
instructions: "Implement the feature described in docs/feature.md."
depends_on:
- "step-1-id"
- name: "Step 3 — test"
agent:
instructions: "Write and run integration tests."
depends_on:
- "step-2-id"
retry:
max_attempts: 2
backoff: "linear"
```
`ParseFile` / `Parse` detect the batch format automatically: if unmarshaling into `BatchFile` succeeds and produces at least one task, the batch path is used; otherwise single-task parsing is attempted.
---
## State Constants
```go
type State string
const (
StatePending State = "PENDING"
StateQueued State = "QUEUED"
StateRunning State = "RUNNING"
StateReady State = "READY"
StateCompleted State = "COMPLETED"
StateFailed State = "FAILED"
StateTimedOut State = "TIMED_OUT"
StateCancelled State = "CANCELLED"
StateBudgetExceeded State = "BUDGET_EXCEEDED"
StateBlocked State = "BLOCKED"
)
```
| Constant | String value | Description |
|-----------------------|-------------------|-------------------------------------------------------------------------------------------------------|
| `StatePending` | `PENDING` | Newly created; awaiting acceptance for scheduling. |
| `StateQueued` | `QUEUED` | Accepted by the scheduler and waiting for an available executor. |
| `StateRunning` | `RUNNING` | An agent is actively executing the task. |
| `StateReady` | `READY` | Agent finished execution; output is awaiting human review/approval. |
| `StateCompleted` | `COMPLETED` | Approved and finished. **Terminal** — no further transitions. |
| `StateFailed` | `FAILED` | Execution failed (non-zero exit or unrecoverable error). Eligible for retry → `QUEUED`. |
| `StateTimedOut` | `TIMED_OUT` | Execution exceeded the configured `timeout`. Eligible for retry or resume → `QUEUED`. |
| `StateCancelled` | `CANCELLED` | Manually cancelled. Can be restarted by transitioning back to `QUEUED`. |
| `StateBudgetExceeded` | `BUDGET_EXCEEDED` | The `max_budget_usd` cap was reached during execution. Eligible for retry → `QUEUED`. |
| `StateBlocked` | `BLOCKED` | The running agent posted a question requiring a human response before it can continue. |
---
## State Machine
### Valid Transitions
| From | To | Condition |
|---------------------|---------------------|-------------------------------------------------------------------|
| `PENDING` | `QUEUED` | Task accepted for scheduling. |
| `PENDING` | `CANCELLED` | Manually cancelled before queuing. |
| `QUEUED` | `RUNNING` | An executor picks up the task. |
| `QUEUED` | `CANCELLED` | Manually cancelled while waiting in queue. |
| `RUNNING` | `READY` | Agent finished; output ready for human review. |
| `RUNNING` | `COMPLETED` | Agent finished successfully with no review required. |
| `RUNNING` | `FAILED` | Agent exited with an error. |
| `RUNNING` | `TIMED_OUT` | Wall-clock timeout exceeded. |
| `RUNNING` | `CANCELLED` | Manually cancelled mid-execution. |
| `RUNNING` | `BUDGET_EXCEEDED` | API spend reached `max_budget_usd`. |
| `RUNNING` | `BLOCKED` | Agent posted a question requiring human input before continuing. |
| `READY` | `COMPLETED` | Reviewer approves the output. |
| `READY` | `PENDING` | Reviewer rejects the output; task sent back for revision. |
| `FAILED` | `QUEUED` | Retry: task re-queued for another attempt. |
| `TIMED_OUT` | `QUEUED` | Retry or resume: task re-queued. |
| `CANCELLED` | `QUEUED` | Restart: task re-queued from scratch. |
| `BUDGET_EXCEEDED` | `QUEUED` | Retry with adjusted budget. |
| `BLOCKED` | `QUEUED` | Answer provided; task re-queued to resume. |
| `BLOCKED` | `READY` | Question resolved; output is ready for review. |
| `COMPLETED` | *(none)* | Terminal state — no outgoing transitions. |
---
## Key Functions
### `ParseFile`
```go
func ParseFile(path string) ([]Task, error)
```
Reads a YAML file at `path` and returns the parsed tasks. Supports both single-task and batch formats. Initialises defaults (UUID, priority, retry policy, initial state, timestamps) on every returned task.
### `Parse`
```go
func Parse(data []byte) ([]Task, error)
```
Same as `ParseFile` but operates on raw YAML bytes instead of a file path.
### `ValidTransition`
```go
func ValidTransition(from, to State) bool
```
Returns `true` if transitioning from `from` to `to` is permitted by the state machine. Used by `storage.DB.UpdateTaskState` to enforce valid transitions inside a transaction.
### `Validate`
```go
func Validate(t *Task) error
```
Checks a task for constraint violations. Returns a `*ValidationError` (implementing `error`) listing all violations, or `nil` if the task is valid. All violations are collected before returning so callers receive the full list at once.
---
## Validation Rules
| Rule | Error message |
|------------------------------------------------------------------------------|--------------------------------------------------------------------|
| `name` must be non-empty. | `name is required` |
| `agent.instructions` must be non-empty. | `agent.instructions is required` |
| `agent.max_budget_usd` must be ≥ 0. | `agent.max_budget_usd must be non-negative` |
| `timeout` must be ≥ 0. | `timeout must be non-negative` |
| `retry.max_attempts` must be ≥ 1. | `retry.max_attempts must be at least 1` |
| `retry.backoff`, if set, must be `linear` or `exponential`. | `retry.backoff must be 'linear' or 'exponential'` |
| `priority`, if set, must be `high`, `normal`, or `low`. | `invalid priority "…"; must be high, normal, or low` |
| `agent.permission_mode`, if set, must be one of the six recognised values. | `invalid permission_mode "…"` |
|