Skip to content

Commit 8658af3

Browse files
chore: professionalize Python SDK documentation
- Update AGENTS.md with file tree diagram, client API reference, key file locations - Rewrite README.md with features list, expanded quick start, API reference, error handling - Standardize CODE_OF_CONDUCT.md and SECURITY.md across ecosystem Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 615c710 commit 8658af3

2 files changed

Lines changed: 247 additions & 27 deletions

File tree

AGENTS.md

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,32 @@ mypy . # Type check
2222

2323
## Architecture
2424

25-
- `hawk/client.py` — Main `HawkClient` class
26-
- `hawk/types.py` — Pydantic models for API responses
27-
- `hawk/errors.py` — Typed error classes (`HawkAPIError`, etc.)
28-
- `hawk/discovery.py` — Auto-discover running hawk daemon
29-
- `hawk/memory_tools.py` — Memory graph operations
30-
- `tests/` — Test suite with mocked HTTP responses
25+
```
26+
hawk-sdk-python/
27+
├── src/hawk/
28+
│ ├── __init__.py # Public API exports
29+
│ ├── _version.py # Version from VERSION file
30+
│ ├── client.py # HawkClient (sync) + AsyncHawkClient (async)
31+
│ ├── types.py # Pydantic models for all API shapes
32+
│ ├── errors.py # Typed error hierarchy + parse_error() factory
33+
│ ├── retry.py # Exponential backoff with jitter
34+
│ ├── streaming.py # StreamReader / AsyncStreamReader (SSE)
35+
│ ├── discovery.py # Auto-discover running hawk daemon
36+
│ ├── memory_tools.py # Memory graph operations
37+
│ ├── agent.py # Agent / AsyncAgent abstractions
38+
│ ├── tools.py # Tool decorator, chat_with_tools helpers
39+
│ ├── toolkit.py # BackgroundTask, ToolGroup, Toolkit
40+
│ ├── workflow.py # Workflow / AsyncWorkflow orchestration
41+
│ ├── sessions.py # Session management helpers
42+
│ ├── evaluate.py # Evaluation / benchmarking
43+
│ ├── tracing.py # OTEL tracing integration
44+
│ ├── plan.py # Plan / SubTask models
45+
│ └── retry.py # RetryConfig, with_retry, with_retry_sync
46+
├── tests/ # Test suite with mocked HTTP responses
47+
├── examples/ # Runnable example scripts
48+
├── scripts/ # Boundary guards, tooling scripts
49+
└── docs/ # Architecture documentation
50+
```
3151

3252
## Conventions
3353

@@ -78,6 +98,69 @@ Every public client method wraps its logic in a `_do()` closure passed to `with_
7898
### Dual Client Pattern
7999
`HawkClient` (sync) and `AsyncHawkClient` (async) are structurally identical. Every method exists on both. Sync uses `httpx.Client` + `with_retry_sync`; async uses `httpx.AsyncClient` + `with_retry`. When adding a new method, add it to both classes with identical signatures (minus `async`/`await`).
80100

101+
## Client API Reference
102+
103+
### HawkClient / AsyncHawkClient
104+
105+
Both clients share the same constructor and method signatures.
106+
107+
```python
108+
HawkClient(
109+
base_url: str = "http://127.0.0.1:4590",
110+
api_key: str | None = None,
111+
retry_config: RetryConfig | None = None,
112+
timeout: float = 30.0,
113+
pool_connections: int = 10,
114+
pool_maxsize: int = 100,
115+
)
116+
```
117+
118+
**Methods:**
119+
120+
| Method | Path | Returns | Description |
121+
|--------|------|---------|-------------|
122+
| `health()` | `GET /v1/health` | `HealthResponse` | Check daemon connectivity and version |
123+
| `chat(prompt)` | `POST /v1/chat` | `ChatResponse` | Send a prompt, get complete response |
124+
| `chat_stream(prompt)` | `POST /v1/chat` | `StreamReader` | Send a prompt, stream SSE response |
125+
| `get_session(session_id)` | `GET /v1/sessions/{id}` | `SessionDetail` | Get full session detail |
126+
| `list_sessions()` | `GET /v1/sessions` | `list[SessionSummary]` | List active sessions |
127+
| `delete_session(session_id)` | `DELETE /v1/sessions/{id}` | `None` | Delete a session |
128+
| `list_messages(session_id)` | `GET /v1/sessions/{id}/messages` | `PaginatedResponse[Message]` | List messages with pagination |
129+
| `stats()` | `GET /v1/stats` | `StatsResponse` | Get aggregated usage statistics |
130+
131+
### Typed Errors
132+
133+
All API errors inherit from `HawkAPIError`. Catch specific subclasses:
134+
135+
```python
136+
from hawk import HawkAPIError, RateLimitError, AuthenticationError, NotFoundError
137+
138+
# HawkAPIError (base class)
139+
# RateLimitError — 429, has .retry_after property
140+
# AuthenticationError — 401
141+
# ForbiddenError — 403
142+
# BadRequestError — 400
143+
# NotFoundError — 404
144+
# InternalServerError — 500
145+
# ServiceUnavailableError — 503
146+
```
147+
148+
### StreamReader / AsyncStreamReader
149+
150+
```python
151+
with HawkClient() as client:
152+
with client.chat_stream("Hello") as stream:
153+
# Iterate events
154+
for event in stream.events():
155+
print(event.data)
156+
157+
# Collect all text
158+
text = stream.collect_text()
159+
160+
# Collect tool calls
161+
calls = stream.collect_tool_calls()
162+
```
163+
81164
## Testing Patterns
82165

83166
### HTTP Mocking
@@ -113,11 +196,14 @@ SSE body strings follow the format `"event: content\ndata: Hello\n\n"`. Tests ve
113196
| SSE streaming | `src/hawk/streaming.py` |
114197
| Agent abstraction | `src/hawk/agent.py` |
115198
| Tool decorator + loop | `src/hawk/tools.py` |
199+
| Toolkit | `src/hawk/toolkit.py` |
116200
| Workflow engine | `src/hawk/workflow.py` |
201+
| Sessions | `src/hawk/sessions.py` |
117202
| Agent discovery | `src/hawk/discovery.py` |
118203
| Memory graph ops | `src/hawk/memory_tools.py` |
119204
| Evaluation/benchmarks | `src/hawk/evaluate.py` |
120205
| Tracing/observability | `src/hawk/tracing.py` |
206+
| Plan module | `src/hawk/plan.py` |
121207
| Client tests | `tests/test_client.py` |
122208
| Error tests | `tests/test_errors.py` |
123209
| Retry tests | `tests/test_retry.py` |

README.md

Lines changed: 155 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,20 @@
1212

1313
---
1414

15-
The Hawk SDK for Python provides an idiomatic client for interacting with the [Hawk](https://github.com/GrayCodeAI/hawk) daemon API. It supports both synchronous and async operations, including streaming.
15+
The Hawk SDK for Python is the official client for interacting with the [Hawk](https://github.com/GrayCodeAI/hawk)
16+
daemon API. It provides an idiomatic Python interface for chat, streaming, sessions,
17+
stats, and agent orchestration. Both synchronous and async clients are provided, with
18+
the same API surface on each.
1619

1720
## Features
1821

19-
- **Sync and async** - Use `HawkClient` or `AsyncHawkClient`
20-
- **Streaming support** - Real-time response streaming
21-
- **Context managers** - Automatic resource cleanup
22-
- **Type hints** - Full type annotations for IDE support
23-
- **Error handling** - Detailed exception types
22+
- **Dual client pattern** - `HawkClient` (sync) and `AsyncHawkClient` with identical method signatures
23+
- **Streaming** - SSE-based real-time chat streaming via `StreamReader` / `AsyncStreamReader`
24+
- **Type safety** - Pydantic v2 models with full type hints (Python 3.9+)
25+
- **Context managers** - `with HawkClient() as client:` for automatic resource cleanup
26+
- **Typed errors** - Status-code-based exception hierarchy (`AuthenticationError`, `NotFoundError`, `RateLimitError`, etc.)
27+
- **Retry with backoff** - Configurable exponential backoff with jitter via `RetryConfig`
28+
- **Minimal dependencies** - `httpx` (HTTP), `pydantic` v2, and `eval-type-backport` (Python < 3.10)
2429

2530
## Installation
2631

@@ -30,52 +35,181 @@ pip install hawk-sdk
3035

3136
## Quick Start
3237

38+
### Synchronous
39+
3340
```python
3441
from hawk import HawkClient
3542

3643
with HawkClient() as client:
3744
# Health check
3845
health = client.health()
39-
print(f"Version: {health.version}")
46+
print(f"Status: {health.status}, Version: {health.version}")
47+
print(f"Active sessions: {health.active_sessions}")
4048

4149
# Chat
4250
response = client.chat("Explain decorators in Python")
4351
print(response.response)
52+
print(f"Tokens: in={response.tokens_in}, out={response.tokens_out}")
4453
```
4554

46-
## Async Usage
55+
### Asynchronous
4756

4857
```python
58+
import asyncio
4959
from hawk import AsyncHawkClient
5060

51-
async with AsyncHawkClient() as client:
52-
response = await client.chat("Hello!")
53-
print(response.response)
61+
async def main():
62+
async with AsyncHawkClient() as client:
63+
health = await client.health()
64+
print(f"Status: {health.status}, Version: {health.version}")
65+
66+
response = await client.chat("Hello!")
67+
print(response.response)
68+
69+
asyncio.run(main())
5470
```
5571

56-
## Streaming
72+
### Streaming
5773

5874
```python
75+
from hawk import HawkClient
76+
5977
with HawkClient() as client:
60-
with client.chat_stream("Write a haiku") as stream:
78+
with client.chat_stream("Write a haiku about coding") as stream:
6179
for event in stream.events():
62-
print(event.data, end="", flush=True)
80+
if event.event is None or event.event == "content":
81+
print(event.data, end="", flush=True)
82+
print()
83+
84+
# Or collect all text at once
85+
with client.chat_stream("Write a haiku about coding") as stream:
86+
text = stream.collect_text()
87+
print(f"Full response: {text}")
88+
89+
# Or collect tool calls
90+
with client.chat_stream("What tools do I have?", tools=[{"type": "list"}]) as stream:
91+
calls = stream.collect_tool_calls()
92+
for call in calls:
93+
print(f"Tool: {call.name}({call.arguments})")
94+
```
95+
96+
## API Reference
97+
98+
### HawkClient / AsyncHawkClient
99+
100+
Both clients share the same constructor and method signatures.
101+
102+
#### Constructor
103+
104+
```python
105+
HawkClient(
106+
base_url: str = "http://127.0.0.1:4590",
107+
api_key: str | None = None,
108+
retry_config: RetryConfig | None = None,
109+
timeout: float = 30.0,
110+
pool_connections: int = 10,
111+
pool_maxsize: int = 100,
112+
)
63113
```
64114

65-
## Examples
115+
| Parameter | Default | Description |
116+
|-----------|---------|-------------|
117+
| `base_url` | `http://127.0.0.1:4590` | Base URL of the Hawk daemon |
118+
| `api_key` | `None` | Bearer token for authenticated requests |
119+
| `retry_config` | `DEFAULT_RETRY_CONFIG` | Retry configuration (429, 5xx, etc.) |
120+
| `timeout` | `30.0` | HTTP request timeout in seconds |
121+
| `pool_connections` | `10` | Max keep-alive connections |
122+
| `pool_maxsize` | `100` | Max total connections |
123+
124+
#### Methods
125+
126+
```python
127+
# Health
128+
health() -> HealthResponse
129+
130+
# Chat
131+
chat(
132+
prompt: str,
133+
session_id: str | None = None,
134+
model: str | None = None,
135+
max_turns: int | None = None,
136+
autonomy: str | None = None,
137+
cwd: str | None = None,
138+
agent: str | None = None,
139+
tools: list[dict[str, Any]] | None = None,
140+
tool_results: list[ToolResult] | None = None,
141+
) -> ChatResponse
142+
143+
# Streaming chat
144+
chat_stream(
145+
prompt: str,
146+
session_id: str | None = None,
147+
model: str | None = None,
148+
max_turns: int | None = None,
149+
autonomy: str | None = None,
150+
cwd: str | None = None,
151+
agent: str | None = None,
152+
tools: list[dict[str, Any]] | None = None,
153+
tool_results: list[ToolResult] | None = None,
154+
) -> StreamReader # or AsyncStreamReader
155+
156+
# Session management
157+
get_session(session_id: str) -> SessionDetail
158+
list_sessions() -> list[SessionSummary]
159+
delete_session(session_id: str) -> None
160+
161+
# Messages with pagination
162+
list_messages(
163+
session_id: str,
164+
limit: int = 50,
165+
offset: int = 0,
166+
) -> PaginatedResponse[Message]
167+
168+
# Stats
169+
stats() -> StatsResponse
170+
```
171+
172+
### Typed Errors
173+
174+
All API errors inherit from `HawkAPIError`. Catch specific subclasses:
175+
176+
```python
177+
from hawk import HawkClient, HawkAPIError, RateLimitError, AuthenticationError
178+
179+
with HawkClient() as client:
180+
try:
181+
response = client.chat("Hello")
182+
except AuthenticationError:
183+
print("Invalid API key")
184+
except RateLimitError as e:
185+
print(f"Rate limited, retry after {e.retry_after}s")
186+
except HawkAPIError as e:
187+
print(f"API error {e.status_code}: {e.message}")
188+
```
189+
190+
Error classes: `BadRequestError` (400), `AuthenticationError` (401), `ForbiddenError` (403),
191+
`NotFoundError` (404), `RateLimitError` (429), `InternalServerError` (500),
192+
`ServiceUnavailableError` (503).
193+
194+
## Error Handling
195+
196+
Every public method wraps its logic in a retry closure that automatically:
66197

67-
See the [examples/](examples/) directory for complete runnable examples.
198+
- Respects `Retry-After` headers on 429 responses
199+
- Uses exponential backoff with jitter for retryable statuses (429, 500, 502, 503, 504)
200+
- Raises typed exceptions matching the HTTP status code
68201

69202
## Ecosystem Boundaries
70203

71-
- `hawk-sdk-python` is a consumer of Hawk public APIs and contracts.
72-
- Do not couple the SDK to support engine repos such as `eyrie`, `yaad`, `tok`, `trace`, `sight`, or `inspect`.
73-
- Do not reference `hawk/internal/*` or removed legacy path `hawk/shared/types`.
74-
- If a capability is needed across repos, expose it through Hawk or `hawk-core-contracts`, not engine internals.
204+
`hawk-sdk-python` is a consumer of Hawk public APIs and contracts. It does not
205+
import from engine repos such as `eyrie`, `yaad`, `tok`, `trace`, `sight`, or
206+
`inspect`. If a capability is needed across repos, it should be exposed through
207+
Hawk or `hawk-core-contracts`, not through engine internals.
75208

76209
## Contributing
77210

78-
Contributions are welcome — please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.
211+
Contributions are welcome — please read [CONTRIBUTING.md](CONTRIBUTING.md) before
212+
opening a pull request.
79213

80214
## License
81215

0 commit comments

Comments
 (0)