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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ concurrency:
cancel-in-progress: true

env:
GO_VERSION: "1.26.5"
GO_VERSION: "1.26.6"

jobs:
daemon-contract:
Expand Down
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.

### Changed
- **BREAKING: `Sessions()` now returns `[]SessionSummary`** and takes no
`ListOptions`. The daemon's `GET /v1/sessions` returns a bare JSON array
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
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: 1 addition & 1 deletion go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions internal/spec/openapi_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading