# 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 "…"` |