Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **"Defaults & divergences across SDKs" README section** documenting how
retry, backoff, jitter, and timeout defaults differ between the Go,
TypeScript, and Python SDKs.

## [0.2.0] — 2026-07-14

### Changed
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ with HawkClient() as client:
print(f"Tool: {call.name}({call.arguments})")
```

## Defaults & divergences across SDKs

The three Hawk SDKs (Go, TypeScript, Python) share wire behavior but have
drifted in transport defaults. Actual current values:

| Default | Python (this SDK) | Go | TypeScript |
| --- | --- | --- | --- |
| Retries | **On** — `retry_config or DEFAULT_RETRY_CONFIG` (`src/hawk/client.py`, both sync and async clients) | **Off** — opt in with `WithRetry(DefaultRetryConfig())` (`client.go`) | **Off** — opt in with `{ retry: defaultRetryConfig() }` (`src/client.ts`) |
| Initial backoff | 0.5s (`src/hawk/retry.py`, `RetryConfig`) | 1s (`retry.go`, `DefaultRetryConfig`) | 1s (`src/retry.ts`, `defaultRetryConfig`) |
| Backoff jitter | Equal + jitter: `backoff + rand(0, backoff/2)` (`src/hawk/retry.py`, `_compute_backoff`) | Full jitter: `rand(0, backoff)` (`retry.go`, `backoffDuration`) | Full jitter: `rand(0, backoff)` (`src/retry.ts`, `backoffDurationMs`) |
| Request timeout | httpx timeout, 30s (`src/hawk/client.py`, `DEFAULT_TIMEOUT`) | `ResponseHeaderTimeout: 5s`, headers only (`client.go`) | Whole-request deadline, 30s, includes retries (`src/client.ts`, `timeoutMs`) |

Max retries (3), max backoff (30s), retryable statuses (429/500/502/503/504),
and the non-idempotent rule (only 429 is retried for POST `/v1/chat`) are
identical in all three SDKs. This table documents current behavior; it is not
a compatibility contract between the SDKs.

## API Reference

### HawkClient / AsyncHawkClient
Expand Down
111 changes: 110 additions & 1 deletion api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ info:
url: https://github.com/GrayCodeAI/hawk

servers:
- url: http://localhost:4590
- url: http://127.0.0.1:4590
description: Local daemon (default port)

security:
Expand Down Expand Up @@ -417,6 +417,7 @@ tags:
paths:
/v1/health:
get:
operationId: healthCheck
tags: [system]
summary: Health check
security: []
Expand All @@ -427,9 +428,16 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
"400":
description: Invalid request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"

/v1/ready:
get:
operationId: readinessProbe
tags: [system]
summary: Readiness probe
description: |
Expand All @@ -444,6 +452,12 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/ReadyResponse"
"400":
description: Invalid request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"503":
description: Daemon is not ready
content:
Expand All @@ -453,6 +467,7 @@ paths:

/v1/chat:
post:
operationId: sendChat
tags: [agent]
summary: Send a prompt to the agent
description: |
Expand Down Expand Up @@ -508,8 +523,63 @@ paths:
schema:
$ref: "#/components/schemas/Error"

/v1/cancel:
post:
operationId: cancelGeneration
tags: [agent]
summary: Cancel an in-flight generation
description: |
Aborts the active generation for the given session, if one is running.
The per-session generation is otherwise serialized (one at a time);
this endpoint lets a caller stop a long-running response early.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [session_id]
properties:
session_id:
type: string
responses:
"200":
description: Generation cancelled (or completed before the request was processed)
content:
application/json:
schema:
type: object
properties:
cancelled:
type: boolean
"400":
description: Invalid or missing session_id
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"404":
description: No active generation for the session
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"429":
description: Rate limit exceeded
content:
application/json:
schema:
$ref: "#/components/schemas/Error"

/v1/sessions:
get:
operationId: listSessions
tags: [sessions]
summary: List active daemon sessions
responses:
Expand All @@ -530,6 +600,7 @@ paths:

/v1/sessions/{id}:
get:
operationId: getSession
tags: [sessions]
summary: Get a persisted session
parameters:
Expand All @@ -552,6 +623,7 @@ paths:
schema:
$ref: "#/components/schemas/Error"
delete:
operationId: deleteSession
tags: [sessions]
summary: Delete a session
parameters:
Expand All @@ -578,6 +650,7 @@ paths:

/v1/sessions/{id}/messages:
get:
operationId: listSessionMessages
tags: [messages]
summary: Get session messages with pagination
parameters:
Expand Down Expand Up @@ -610,6 +683,7 @@ paths:

/v1/sessions/{id}/graph:
get:
operationId: getSessionGraph
tags: [graphs]
summary: Project a persisted session as a portable execution graph
description: |
Expand Down Expand Up @@ -676,6 +750,7 @@ paths:

/v1/stats:
get:
operationId: getStats
tags: [stats]
summary: Aggregated usage statistics
responses:
Expand All @@ -692,8 +767,35 @@ paths:
schema:
$ref: "#/components/schemas/Error"

/v1/metrics:
get:
operationId: getMetrics
tags: [stats]
summary: Daemon metrics in Prometheus exposition format
description: |
Returns daemon-level metrics (request counts, concurrency usage,
active sessions) as Prometheus text exposition format.
Use `?format=json` for JSON output.
responses:
"200":
description: Metrics output
content:
text/plain:
schema:
type: string
application/json:
schema:
type: object
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/Error"

/v1/review:
post:
operationId: createReview
tags: [review]
summary: Trigger an asynchronous code review of a commit
requestBody:
Expand All @@ -718,6 +820,7 @@ paths:

/v1/review/status:
get:
operationId: getReviewStatus
tags: [review]
summary: Get current review status
responses:
Expand All @@ -727,6 +830,12 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/ReviewStatusResponse"
"400":
description: Invalid request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"500":
description: Status command failed
content:
Expand Down
2 changes: 2 additions & 0 deletions tests/test_openapi_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ def test_every_daemon_path_has_an_sdk_support_decision() -> None:
"/v1/stats": "supported",
"/v1/review": "unsupported: asynchronous review orchestration",
"/v1/review/status": "unsupported: review worker status",
"/v1/cancel": "unsupported: in-flight request cancellation",
"/v1/metrics": "unsupported: daemon metrics endpoint",
}
assert paths == sorted(decisions)
Loading