Previous bug: only code:* and review labels were checked, so agent:document, agent:tests etc. were never filtered. Any agent could pick up any task. Now: labels with agent: prefix are matched against agent capabilities. Other labels are treated as metadata. Includes regression test.
478 lines
11 KiB
Markdown
478 lines
11 KiB
Markdown
# Agent Fleet — HTTP API Reference
|
|
|
|
`FLEET_API_URL:PORT` means the address of your Agent Fleet Orchestrator (for example, `100.102.101.43:9090`). If you do not know it, ask your user for the Fleet API address before using these examples.
|
|
|
|
Base URL: `http://FLEET_API_URL:PORT`
|
|
Content-Type: `application/json` for all request/response bodies unless noted.
|
|
All timestamps are ISO 8601 (RFC 3339).
|
|
If you do not know the Fleet API URL, ask the user who deployed the orchestrator.
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
### http_pull Bearer Token
|
|
|
|
Endpoints that are specific to `http_pull` agents require a Bearer token in the `Authorization` header. The token is configured in `config.toml` as `orchestrator.http_pull_token`. If no token is configured in the config, authentication is skipped (open mode).
|
|
|
|
```
|
|
Authorization: Bearer <http_pull_token>
|
|
```
|
|
|
|
Affected endpoints: `POST /api/v1/tasks/dequeue`, `POST /api/v1/tasks/{task_id}/status`.
|
|
|
|
### Webhook HMAC-SHA256
|
|
|
|
The `POST /api/v1/webhooks/forgejo` endpoint requires an `X-Gitea-Signature` or `X-Forgejo-Signature` header containing `sha256=<hex_hmac>` of the request body using the configured `webhook_secret`.
|
|
|
|
```
|
|
X-Forgejo-Signature: sha256=abcdef...
|
|
```
|
|
|
|
---
|
|
|
|
## Error Responses
|
|
|
|
All errors return JSON:
|
|
|
|
```json
|
|
{ "error": "<human-readable message>" }
|
|
```
|
|
|
|
| Status | Meaning | Trigger |
|
|
|--------|---------|---------|
|
|
| 400 | Bad Request | Invalid state transition, wrong execution_mode, malformed input |
|
|
| 401 | Unauthorized | Missing or invalid Bearer token for http_pull endpoints |
|
|
| 404 | Not Found | Task or agent does not exist |
|
|
| 500 | Internal Server Error | Database failure, lock poisoning, unexpected errors |
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### Health Check
|
|
|
|
```
|
|
GET /healthz
|
|
```
|
|
|
|
**Response:** `200 OK` — body: `ok`
|
|
|
|
```bash
|
|
curl http://FLEET_API_URL:PORT/healthz
|
|
```
|
|
|
|
---
|
|
|
|
### Register Agent
|
|
|
|
```
|
|
POST /api/v1/agents/register
|
|
```
|
|
|
|
Register a new agent or update an existing one (upsert by `agent_id`).
|
|
|
|
**Request:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| agent_id | string | yes | Unique identifier |
|
|
| agent_type | string | yes | `openclaw`, `claude-code`, `codex-cli`, `hermes`, `acp`, `shell`, or custom |
|
|
| hostname | string | yes | Machine hostname |
|
|
| capabilities | string[] | yes | e.g. `["code:rust", "review"]` |
|
|
| max_concurrency | u32 | yes | Max parallel tasks |
|
|
| metadata | object | no | Arbitrary key-value pairs |
|
|
|
|
**Response:** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"agent_id": "worker-01",
|
|
"registry_token": "registry_a1b2c3d4..."
|
|
}
|
|
```
|
|
|
|
```bash
|
|
curl -X POST http://FLEET_API_URL:PORT/api/v1/agents/register \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{
|
|
"agent_id": "worker-01",
|
|
"agent_type": "codex-cli",
|
|
"hostname": "host-worker-01",
|
|
"capabilities": ["code:rust"],
|
|
"max_concurrency": 2
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
### Heartbeat
|
|
|
|
```
|
|
POST /api/v1/agents/heartbeat
|
|
```
|
|
|
|
This endpoint MUST be called periodically (default: every 60s). A single call is not sufficient. Missing heartbeats will cause the agent to be marked offline and its tasks requeued.
|
|
|
|
**Request:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| agent_id | string | yes | Agent to update |
|
|
|
|
**Response:** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"agent_id": "worker-01",
|
|
"status": "online",
|
|
"last_heartbeat_at": "2025-01-15T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
**Errors:** `404` if agent not found.
|
|
|
|
```bash
|
|
curl -X POST http://FLEET_API_URL:PORT/api/v1/agents/heartbeat \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"agent_id": "worker-01"}'
|
|
```
|
|
|
|
---
|
|
|
|
### Deregister Agent
|
|
|
|
```
|
|
POST /api/v1/agents/deregister
|
|
```
|
|
|
|
Sets agent offline and requeues all its active tasks back to `created`.
|
|
|
|
**Request:**
|
|
|
|
| Field | Type | Required |
|
|
|-------|------|----------|
|
|
| agent_id | string | yes |
|
|
|
|
**Response:** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"agent_id": "worker-01",
|
|
"status": "offline",
|
|
"requeued_tasks": 3
|
|
}
|
|
```
|
|
|
|
```bash
|
|
curl -X POST http://FLEET_API_URL:PORT/api/v1/agents/deregister \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"agent_id": "worker-01"}'
|
|
```
|
|
|
|
---
|
|
|
|
### List Agents
|
|
|
|
```
|
|
GET /api/v1/agents
|
|
```
|
|
|
|
**Query Parameters:**
|
|
|
|
| Param | Type | Description |
|
|
|-------|------|-------------|
|
|
| capability | string | Filter by capability (e.g. `code:rust`) |
|
|
| status | string | Filter: `online`, `offline`, `draining` |
|
|
|
|
**Response:** `200 OK` — JSON array of [Agent](#agent-object) objects.
|
|
|
|
```bash
|
|
curl 'http://FLEET_API_URL:PORT/api/v1/agents?status=online'
|
|
```
|
|
|
|
---
|
|
|
|
### List Tasks
|
|
|
|
```
|
|
GET /api/v1/tasks
|
|
```
|
|
|
|
**Query Parameters:**
|
|
|
|
| Param | Type | Description |
|
|
|-------|------|-------------|
|
|
| status | string | Filter by status (e.g. `created`, `running`, `failed`) |
|
|
| agent_id | string | Filter by assigned agent |
|
|
|
|
**Response:** `200 OK` — JSON array of [Task](#task-object) objects. Ordered by `created_at` descending.
|
|
|
|
```bash
|
|
curl 'http://FLEET_API_URL:PORT/api/v1/tasks?status=running'
|
|
```
|
|
|
|
---
|
|
|
|
### Get Task
|
|
|
|
```
|
|
GET /api/v1/tasks/{task_id}
|
|
```
|
|
|
|
**Response:** `200 OK` — single [Task](#task-object) object.
|
|
|
|
**Errors:** `404` if task not found.
|
|
|
|
```bash
|
|
curl http://FLEET_API_URL:PORT/api/v1/tasks/org%2Frepo%2342
|
|
```
|
|
|
|
---
|
|
|
|
### Dequeue Task (http_pull only)
|
|
|
|
```
|
|
POST /api/v1/tasks/dequeue
|
|
```
|
|
|
|
Requires Bearer token if `http_pull_token` is configured. Only returns tasks with `execution_mode = http_pull`.
|
|
|
|
**Request:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| agent_id | string | yes | Agent claiming the task |
|
|
| capabilities | string[] | no | Capabilities to match against task labels |
|
|
|
|
**Response:** `200 OK` with [Task](#task-object) object, or `204 No Content` if no matching task.
|
|
|
|
**Errors:** `401` if token required and missing/invalid.
|
|
|
|
```bash
|
|
curl -X POST http://FLEET_API_URL:PORT/api/v1/tasks/dequeue \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'Authorization: Bearer my-token' \
|
|
-d '{"agent_id": "worker-03", "capabilities": ["code:rust"]}'
|
|
```
|
|
|
|
---
|
|
|
|
### Update Task Status (http_pull only)
|
|
|
|
```
|
|
POST /api/v1/tasks/{task_id}/status
|
|
```
|
|
|
|
Requires Bearer token. Only works for tasks with `execution_mode = http_pull`.
|
|
|
|
**Request:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| status | string | yes | Target status: `running`, `review_pending`, etc. |
|
|
|
|
**Response:** `200 OK` — updated [Task](#task-object).
|
|
|
|
**Errors:** `400` if task is not `http_pull` mode or transition is invalid. `404` if task not found.
|
|
|
|
```bash
|
|
curl -X POST http://FLEET_API_URL:PORT/api/v1/tasks/org%2Frepo%2342/status \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'Authorization: Bearer my-token' \
|
|
-d '{"status": "running"}'
|
|
```
|
|
|
|
---
|
|
|
|
### Complete Task
|
|
|
|
```
|
|
POST /api/v1/tasks/{task_id}/complete
|
|
```
|
|
|
|
Works for both `ssh_cli` and `http_pull` tasks. Submit a receipt to mark the task done.
|
|
|
|
**Request:** A [Receipt](#receipt-object) object.
|
|
|
|
**Response:** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"task_id": "org/repo#42",
|
|
"status": "completed"
|
|
}
|
|
```
|
|
|
|
**Errors:** `404` if task not found. `400` if task is not in a completable state.
|
|
|
|
```bash
|
|
curl -X POST http://FLEET_API_URL:PORT/api/v1/tasks/org%2Frepo%2342/complete \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{
|
|
"task_id": "org/repo#42",
|
|
"agent_id": "worker-01",
|
|
"status": "completed",
|
|
"duration_seconds": 120,
|
|
"summary": "Implemented feature X",
|
|
"artifacts": [
|
|
{"artifact_type": "pr", "url": "https://git.example/org/repo/pulls/7"}
|
|
],
|
|
"error": null
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
### Retry Task
|
|
|
|
```
|
|
POST /api/v1/tasks/{task_id}/retry
|
|
```
|
|
|
|
Retry a `failed` or `agent_lost` task. Transitions it back to `assigned`.
|
|
|
|
**Response:** `200 OK` — updated [Task](#task-object).
|
|
|
|
**Errors:** `400` if task status is not `failed` or `agent_lost`. `404` if task not found.
|
|
|
|
```bash
|
|
curl -X POST http://FLEET_API_URL:PORT/api/v1/tasks/org%2Frepo%2342/retry
|
|
```
|
|
|
|
---
|
|
|
|
### Submit Receipt
|
|
|
|
```
|
|
POST /api/v1/receipts
|
|
```
|
|
|
|
Submit a receipt for a task. Validates artifacts (e.g. checks PR exists via Forgejo API).
|
|
|
|
**Request:** A [Receipt](#receipt-object) object.
|
|
|
|
**Response:** `200 OK`
|
|
|
|
**Errors:** `404` if task not found. `400` if validation fails.
|
|
|
|
```bash
|
|
curl -X POST http://FLEET_API_URL:PORT/api/v1/receipts \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{
|
|
"task_id": "org/repo#42",
|
|
"agent_id": "worker-01",
|
|
"status": "completed",
|
|
"duration_seconds": 95,
|
|
"summary": "Fixed the bug",
|
|
"artifacts": [],
|
|
"error": null
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
### Forgejo Webhook
|
|
|
|
```
|
|
POST /api/v1/webhooks/forgejo
|
|
```
|
|
|
|
Receives Forgejo webhook events. Requires HMAC-SHA256 signature header.
|
|
|
|
**Headers:** `X-Forgejo-Event` or `X-Gitea-Event` determines the event type.
|
|
|
|
**Supported events:**
|
|
|
|
| Event | Action |
|
|
|-------|--------|
|
|
| `issues` (opened) | Creates a task from the Issue (requires `agent:*` label) |
|
|
| `pull_request` (opened) | Sets task to `review_pending` (branch name → task_id) |
|
|
| `pull_request` (merged/closed with `merged: true`) | Sets task to `completed`, auto-generates receipt |
|
|
| `push` (to `task/*` branch) | Updates `last_activity_at` on the task |
|
|
|
|
**Response:** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"accepted": true,
|
|
"task_id": "org/repo#42"
|
|
}
|
|
```
|
|
|
|
**Errors:** `401` if signature invalid. `400` if payload unparseable.
|
|
|
|
---
|
|
|
|
## Object Schemas
|
|
|
|
### Agent Object
|
|
|
|
```json
|
|
{
|
|
"agent_id": "worker-01",
|
|
"agent_type": "codex-cli",
|
|
"hostname": "host-worker-01",
|
|
"capabilities": ["code:rust"],
|
|
"max_concurrency": 2,
|
|
"current_tasks": 1,
|
|
"status": "online",
|
|
"last_heartbeat_at": "2025-01-15T10:30:00Z",
|
|
"registered_at": "2025-01-15T09:00:00Z",
|
|
"metadata": {}
|
|
}
|
|
```
|
|
|
|
### Task Object
|
|
|
|
```json
|
|
{
|
|
"task_id": "org/repo#42",
|
|
"source": "forgejo:org/repo#42",
|
|
"task_type": "code",
|
|
"priority": "normal",
|
|
"status": "created",
|
|
"execution_mode": "ssh_cli",
|
|
"assigned_agent_id": null,
|
|
"assigned_host": null,
|
|
"requirements": "Implement the feature described in the issue body",
|
|
"labels": ["agent:code", "code:rust"],
|
|
"branch_name": "task/org%2Frepo%2342",
|
|
"pr_title": "feat: Implement feature (#42)",
|
|
"created_at": "2025-01-15T10:00:00Z",
|
|
"assigned_at": null,
|
|
"started_at": null,
|
|
"completed_at": null,
|
|
"last_activity_at": null,
|
|
"retry_count": 0,
|
|
"max_retries": 2,
|
|
"review_count": 0,
|
|
"timeout_seconds": 1800
|
|
}
|
|
```
|
|
|
|
**Status values:** `created`, `assigned`, `running`, `review_pending`, `completed`, `failed`, `agent_lost`, `cancelled`
|
|
|
|
**Priority values:** `low`, `normal`, `high`, `urgent`
|
|
|
|
**Execution mode values:** `ssh_cli`, `http_pull`
|
|
|
|
### Receipt Object
|
|
|
|
```json
|
|
{
|
|
"task_id": "org/repo#42",
|
|
"agent_id": "worker-01",
|
|
"status": "completed",
|
|
"duration_seconds": 120,
|
|
"summary": "Implemented the feature",
|
|
"artifacts": [
|
|
{"artifact_type": "pr", "url": "https://git.example/org/repo/pulls/7", "path": null, "description": null}
|
|
],
|
|
"error": null
|
|
}
|
|
```
|
|
|
|
**Receipt status values:** `completed`, `failed`, `partial`
|
|
|
|
**Artifact type values:** `pr`, `commit`, `file`, `comment`, `url`
|