agent-fleet/openspec/changes/dynamic-execution-mode/specs/task-lifecycle/spec.md
Zer4tul 48c93e2ce9 feat: dynamic execution mode — Undecided tasks, two-phase dispatch, assign API
- ExecutionMode enum adds Undecided variant (default for new tasks)
- Webhook creates tasks as Undecided instead of hardcoded SshCli
- Dispatch loop: Phase 1 matches ssh_cli hosts, Phase 2 marks remaining as HttpPull
- Dequeue now returns http_pull AND undecided tasks (atomic claim)
- New endpoint: POST /api/v1/tasks/{id}/assign for coordinator explicit assignment
- Backward compatible: existing SshCli/HttpPull tasks unaffected
- 37 tests passing (6 new)
2026-05-13 05:29:12 +08:00

82 lines
4 KiB
Markdown

## ADDED Requirements
### Requirement: ExecutionMode enum includes Undecided variant
`ExecutionMode` enum SHALL include an `Undecided` variant as the default for newly created tasks.
#### Scenario: Task created via Forgejo webhook
- **WHEN** a Forgejo Issue webhook creates a task
- **THEN** `execution_mode` SHALL be `Undecided`
- **AND** the task SHALL be eligible for both ssh_cli dispatch and http_pull dequeue
#### Scenario: Task created via API
- **WHEN** a task is created via direct API call without specifying execution_mode
- **THEN** `execution_mode` SHALL default to `Undecided`
### Requirement: Two-phase dispatch loop
The dispatch loop SHALL use a two-phase approach to handle `Undecided` tasks.
#### Scenario: Undecided task with matching ssh_cli host
- **GIVEN** an `Undecided` task with labels `["agent:code"]`
- **AND** a registered ssh_cli host with agent capabilities matching `["agent:code"]`
- **WHEN** the dispatch loop runs
- **THEN** the task SHALL be assigned `execution_mode = SshCli`
- **AND** the task SHALL be dispatched via SSH for execution
#### Scenario: Undecided task with no matching ssh_cli host
- **GIVEN** an `Undecided` task with labels `["agent:review", "agent:document"]`
- **AND** no registered ssh_cli host with matching capabilities
- **WHEN** the dispatch loop runs
- **THEN** the task SHALL be assigned `execution_mode = HttpPull`
- **AND** the task SHALL become available for http_pull dequeue
#### Scenario: Undecided task with matching ssh_cli host but agent offline
- **GIVEN** an `Undecided` task with matching ssh_cli host
- **AND** the ssh_cli host is unreachable or agent is offline
- **WHEN** the dispatch loop runs
- **THEN** the task SHALL remain `Undecided` (retry next cycle)
- **AND** the task SHALL also be available for http_pull dequeue (fallback)
### Requirement: Coordinator explicit assignment
The API SHALL provide an endpoint for coordinators to explicitly assign tasks to specific agents.
#### Scenario: Coordinator assigns task to specific agent
- **GIVEN** a task in `Created` or `Undecided` status
- **WHEN** coordinator calls `POST /api/v1/tasks/{id}/assign` with `{"agent_id": "hermes-worker-01"}`
- **THEN** the task SHALL be assigned to the specified agent
- **AND** execution_mode SHALL be auto-detected from the agent's registration type (http_pull for registered agents, ssh_cli for configured hosts)
- **AND** the task status SHALL transition to `Assigned`
#### Scenario: Coordinator assigns to non-existent agent
- **WHEN** coordinator calls assign with an unknown agent_id
- **THEN** the API SHALL return 404 Not Found
#### Scenario: Coordinator assigns already-running task
- **WHEN** coordinator calls assign on a task in `Running` or `Completed` status
- **THEN** the API SHALL return 400 Bad Request
### Requirement: Dequeue accepts Undecided tasks
The dequeue endpoint SHALL return tasks with `execution_mode` of either `HttpPull` or `Undecided`.
#### Scenario: Agent dequeues Undecided task
- **GIVEN** an `Undecided` task matching the agent's capabilities
- **WHEN** an http_pull agent calls dequeue
- **THEN** the task SHALL be returned
- **AND** `execution_mode` SHALL be atomically updated to `HttpPull`
- **AND** the task SHALL be assigned to the dequeuing agent
#### Scenario: No race condition between dispatch and dequeue
- **GIVEN** an `Undecided` task
- **WHEN** both ssh_cli dispatch and http_pull dequeue attempt to claim it simultaneously
- **THEN** exactly one SHALL succeed (atomic claim via DB transaction)
- **AND** the other SHALL get no task / skip the task
### Requirement: Backward compatibility
Existing tasks with `execution_mode = SshCli` or `HttpPull` SHALL continue to work without changes.
#### Scenario: Pre-existing SshCli task
- **WHEN** a task already has `execution_mode = SshCli`
- **THEN** the dispatch loop SHALL process it as before (no change)
#### Scenario: Pre-existing HttpPull task
- **WHEN** a task already has `execution_mode = HttpPull`
- **THEN** the dequeue endpoint SHALL return it as before (no change)