From 1740b9fda90ffa2bd8f67a313b1168c433b94d57 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 16 Aug 2026 01:56:45 +0530 Subject: [PATCH 1/3] docs: add SDK defaults divergence notes to README Document the actual retry, backoff, jitter, and timeout defaults of the Go, TypeScript, and Python SDKs with file and symbol references, so the drift between them is visible. No code defaults were changed. --- CHANGELOG.md | 5 +++++ README.md | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2115d77..4b62452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 7b0d2f5..bf4aee9 100644 --- a/README.md +++ b/README.md @@ -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 From 85ae05abd80e7d9e396cafb09fbf2e6e22bd4a7c Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 16 Aug 2026 08:39:21 +0530 Subject: [PATCH 2/3] chore: refresh hawk daemon contract snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sync api/openapi.yaml to hawk main — drift since the last snapshot (110 lines of new endpoints) made the contract check fail on any PR. --- api/openapi.yaml | 111 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index e8bf615..4f81c14 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -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: @@ -417,6 +417,7 @@ tags: paths: /v1/health: get: + operationId: healthCheck tags: [system] summary: Health check security: [] @@ -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: | @@ -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: @@ -453,6 +467,7 @@ paths: /v1/chat: post: + operationId: sendChat tags: [agent] summary: Send a prompt to the agent description: | @@ -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: @@ -530,6 +600,7 @@ paths: /v1/sessions/{id}: get: + operationId: getSession tags: [sessions] summary: Get a persisted session parameters: @@ -552,6 +623,7 @@ paths: schema: $ref: "#/components/schemas/Error" delete: + operationId: deleteSession tags: [sessions] summary: Delete a session parameters: @@ -578,6 +650,7 @@ paths: /v1/sessions/{id}/messages: get: + operationId: listSessionMessages tags: [messages] summary: Get session messages with pagination parameters: @@ -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: | @@ -676,6 +750,7 @@ paths: /v1/stats: get: + operationId: getStats tags: [stats] summary: Aggregated usage statistics responses: @@ -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: @@ -718,6 +820,7 @@ paths: /v1/review/status: get: + operationId: getReviewStatus tags: [review] summary: Get current review status responses: @@ -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: From 166f9f11b45a1f6b303a6e8d4c0d29b4faeb6411 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 16 Aug 2026 08:54:05 +0530 Subject: [PATCH 3/3] test: record support decisions for new /v1/cancel and /v1/metrics paths The refreshed daemon contract snapshot added two endpoints; mark both as explicitly unsupported (with reasons) so the coverage guard passes. --- tests/test_openapi_coverage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_openapi_coverage.py b/tests/test_openapi_coverage.py index a81fc10..04bfcdf 100644 --- a/tests/test_openapi_coverage.py +++ b/tests/test_openapi_coverage.py @@ -20,5 +20,7 @@ def test_every_daemon_path_has_an_sdk_support_decision() -> None: "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/v1/stats": "supported", "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/v1/review": "unsupported: asynchronous review orchestration", "/v1/review/status": "unsupported: review worker status", + "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/v1/cancel": "unsupported: in-flight request cancellation", + "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/v1/metrics": "unsupported: daemon metrics endpoint", } assert paths == sorted(decisions)