From 3bbde89e39b5f7a0e761686ed38e6b4d45f0bb7d Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 16 Aug 2026 01:56:43 +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 9e1dc63..83f1178 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. + ### Changed - **BREAKING: `Sessions()` now returns `[]SessionSummary`** and takes no `ListOptions`. The daemon's `GET /v1/sessions` returns a bare JSON array diff --git a/README.md b/README.md index ddcdec2..8a168cc 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,23 @@ hawk-sdk-go/ - **Single package:** All exported symbols in the `hawksdk` package - **Functional options:** Client configuration via `With*()` functions +## Defaults & divergences across SDKs + +The three Hawk SDKs (Go, TypeScript, Python) share wire behavior but have +drifted in transport defaults. Actual current values: + +| Default | Go (this SDK) | TypeScript | Python | +| --- | --- | --- | --- | +| Retries | **Off** — opt in with `WithRetry(DefaultRetryConfig())` (`client.go`) | **Off** — opt in with `{ retry: defaultRetryConfig() }` (`src/client.ts`) | **On** — `retry_config or DEFAULT_RETRY_CONFIG` (`src/hawk/client.py`) | +| Initial backoff | 1s (`retry.go`, `DefaultRetryConfig`) | 1s (`src/retry.ts`, `defaultRetryConfig`) | 0.5s (`src/hawk/retry.py`, `RetryConfig`) | +| Backoff jitter | Full jitter: `rand(0, backoff)` (`retry.go`, `backoffDuration`) | Full jitter: `rand(0, backoff)` (`src/retry.ts`, `backoffDurationMs`) | Equal + jitter: `backoff + rand(0, backoff/2)` (`src/hawk/retry.py`, `_compute_backoff`) | +| Request timeout | `ResponseHeaderTimeout: 5s`, headers only (`client.go`) | Whole-request deadline, 30s, includes retries (`src/client.ts`, `timeoutMs`) | httpx timeout, 30s (`src/hawk/client.py`, `DEFAULT_TIMEOUT`) | + +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. + ## Quick Start ```go From 634ab06785badd32e0cc3296f04087f1da7a4cd2 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 16 Aug 2026 08:37:44 +0530 Subject: [PATCH 2/3] chore: refresh hawk daemon contract snapshot, bump Go to 1.26.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api/openapi.yaml: sync to hawk main (drift — 110 lines of new endpoints since the last snapshot; the contract check fails on any PR until refreshed) - go.mod + CI: Go 1.26.6 — 1.26.5 stdlib has reachable vulns that fail govulncheck --- .github/workflows/ci.yml | 2 +- api/openapi.yaml | 111 ++++++++++++++++++++++++++++++++++++++- go.mod | 2 +- 3 files changed, 112 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01e7ad0..87c04f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: "1.26.5" + GO_VERSION: "1.26.6" jobs: daemon-contract: 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: diff --git a/go.mod b/go.mod index 550adaf..7dfb1b3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/GrayCodeAI/hawk-sdk-go -go 1.26.5 +go 1.26.6 require github.com/oapi-codegen/oapi-codegen/v2 v2.7.1 From 2f219b00f2144f84b0355b04d23f084e1ce78433 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 16 Aug 2026 08:53:58 +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. --- internal/spec/openapi_coverage_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/spec/openapi_coverage_test.go b/internal/spec/openapi_coverage_test.go index 66a2fa2..eb3c634 100644 --- a/internal/spec/openapi_coverage_test.go +++ b/internal/spec/openapi_coverage_test.go @@ -39,6 +39,8 @@ func TestEveryDaemonPathHasAnSDKSupportDecision(t *testing.T) { "/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", } sort.Strings(paths) var decided []string