feat: add async /jobs surface, state webhooks, and artifact handling
Add the async job submission API, webhook state notifications, and
artifact serving endpoints on top of the Phase 3 queue infrastructure.
Key changes:
- POST /jobs: async job submission with 202 + job_id ULID; optional
state_webhook_url for push notifications on state transitions
- GET /jobs/{id}: job status polling with result, error, and artifact
metadata; artifacts <= 256KB inlined, larger ones by URL reference
- GET /jobs/{id}/artifacts/{name}: raw artifact data serving
- Webhook dispatcher: at-least-once delivery with exponential backoff
(5 retries); optional HMAC-SHA256 signing (X-Foreman-Signature)
- ADR-0014: state_webhook_url only honored on POST /jobs, not sync
/api/chat (caller already blocks for result)
- Comprehensive tests for /jobs lifecycle, webhook delivery, HMAC
verification, artifact inline/URL threshold, and TTL pruning
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# ADR-0014: No webhooks on synchronous /api/chat
|
||||
|
||||
**Status:** Accepted -- 2026-05-23
|
||||
|
||||
## Context
|
||||
|
||||
The `state_webhook_url` field exists on the async `POST /jobs` surface to notify
|
||||
callers of state transitions. When Phase 3 promoted `/api/chat` to route through
|
||||
the internal job queue, the question arose: should webhook events also fire for
|
||||
synchronous chat requests?
|
||||
|
||||
## Decision
|
||||
|
||||
**`state_webhook_url` is only honored on `POST /jobs`.** Synchronous `/api/chat`
|
||||
requests do not fire webhooks, even though they internally create job rows.
|
||||
|
||||
### Rationale
|
||||
|
||||
- The `/api/chat` caller holds an open HTTP connection and blocks until the
|
||||
response is ready. Webhooks would be redundant: the caller already gets the
|
||||
result directly.
|
||||
- Adding webhook delivery on the sync path would double the webhook volume with
|
||||
no consumer benefit.
|
||||
- The sync path is the go-llm target; webhook handling would add latency and
|
||||
complexity to the critical hot path.
|
||||
- Callers who want webhooks should use `POST /jobs` explicitly.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `POST /jobs` is the only entry point that supports `state_webhook_url`.
|
||||
- `/api/chat` job rows are created without a webhook URL and produce no webhook
|
||||
traffic.
|
||||
- This keeps the webhook dispatcher's load proportional to async job volume only.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Fire webhooks on both paths.** Adds webhook traffic for every go-llm request
|
||||
with no consumer; rejected.
|
||||
- **Optional opt-in header on /api/chat.** Over-engineered for a passthrough
|
||||
endpoint; rejected.
|
||||
@@ -25,6 +25,8 @@ worker, one queue. No distributed dispatch, no leases, no fair queueing.
|
||||
| 0010 | Authentication and security boundary | Accepted |
|
||||
| 0011 | Go client library and go-llm integration | Accepted |
|
||||
| 0012 | Streaming support | Accepted |
|
||||
| 0013 | Two-slot residency and embedding bypass | Accepted |
|
||||
| 0014 | No webhooks on synchronous /api/chat | Accepted |
|
||||
|
||||
ADR-0003 was resolved in favor of **native Ollama** as the v1 surface: foreman is,
|
||||
on the wire, a private authenticated Ollama deployment, so `go-llm` integrates via
|
||||
|
||||
Reference in New Issue
Block a user