You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Expand AGENTS.md from 44 to 132 lines with naming conventions,
API patterns, testing patterns, refactoring guidelines, key files
- Add jscpd duplication detection job to CI
All API types live in `src/hawk/types.py`. Every model uses `BaseModel` with `model_config = {"populate_by_name": True}` to allow both alias and field name access. Fields use `Field(alias="snake_case")` to map to the daemon's JSON keys:
61
+
```python
62
+
classChatResponse(BaseModel):
63
+
session_id: str= Field(alias="session_id")
64
+
tokens_in: int= Field(alias="tokens_in")
65
+
model_config = {"populate_by_name": True}
66
+
```
67
+
68
+
### Error Hierarchy
69
+
`errors.py` defines a status-code-based hierarchy: `HawkAPIError` is the base, with subclasses for each HTTP status (400, 401, 403, 404, 429, 500, 503). The `parse_error()` factory reads the JSON body (`error`, `code`, `details` fields) and returns the correct typed error. Always catch specific subclasses, not `HawkAPIError` broadly.
70
+
71
+
### Retry Pattern
72
+
Every public client method wraps its logic in a `_do()` closure passed to `with_retry_sync()` (sync) or `with_retry()` (async). The retry function respects `Retry-After` headers on 429 responses and uses exponential backoff with jitter for other retryable statuses (429, 500, 502, 503, 504).
73
+
74
+
### Streaming
75
+
`StreamReader` and `AsyncStreamReader` wrap `httpx.Response` with SSE parsing. They implement context manager protocol (`with`/`async with`). The `events()` method yields `StreamEvent` objects; `collect_text()` and `collect_tool_calls()` are convenience collectors.
76
+
77
+
### Dual Client Pattern
78
+
`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`).
79
+
80
+
## Testing Patterns
81
+
82
+
### HTTP Mocking
83
+
Tests use `respx` (via pytest fixture `respx_mock`) to mock HTTP responses. Each test registers routes on `BASE_URL = "http://127.0.0.1:4590"`:
Async test classes use `@pytest.mark.asyncio` decorator. The `respx_mock` fixture works for both sync and async.
93
+
94
+
### Error Tests
95
+
`test_errors.py` uses a helper `_make_response()` to construct `httpx.Response` objects with specific status codes, JSON bodies, and headers. Tests verify both the error type (`isinstance`) and properties (`status_code`, `message`, `retry_after`).
96
+
97
+
### Streaming Tests
98
+
SSE body strings follow the format `"event: content\ndata: Hello\n\n"`. Tests verify both `collect_text()` and mid-stream `break` behavior.
99
+
100
+
### Retry Tests
101
+
`test_retry.py` tests retry logic by wrapping call counters in closures. Uses tiny backoff values (`initial_backoff=0.01`) to keep tests fast. Verifies both retry-on-retryable and no-retry-on-non-retryable paths.
102
+
103
+
## Key File Locations
104
+
105
+
| What | Where |
106
+
|------|-------|
107
+
| Public API exports |`src/hawk/__init__.py`|
108
+
| Client (sync + async) |`src/hawk/client.py`|
109
+
| Pydantic models |`src/hawk/types.py`|
110
+
| Error types + parser |`src/hawk/errors.py`|
111
+
| Retry logic |`src/hawk/retry.py`|
112
+
| SSE streaming |`src/hawk/streaming.py`|
113
+
| Agent abstraction |`src/hawk/agent.py`|
114
+
| Tool decorator + loop |`src/hawk/tools.py`|
115
+
| Workflow engine |`src/hawk/workflow.py`|
116
+
| Agent discovery |`src/hawk/discovery.py`|
117
+
| Memory graph ops |`src/hawk/memory_tools.py`|
118
+
| Evaluation/benchmarks |`src/hawk/evaluate.py`|
119
+
| Tracing/observability |`src/hawk/tracing.py`|
120
+
| Client tests |`tests/test_client.py`|
121
+
| Error tests |`tests/test_errors.py`|
122
+
| Retry tests |`tests/test_retry.py`|
123
+
| Streaming tests |`tests/test_streaming.py`|
124
+
125
+
## Refactoring Guidelines
126
+
127
+
-**Safe to refactor**: internal helpers like `_build_headers`, `_validate_base_url`, `_compute_backoff` — they are private and well-tested
128
+
-**Do not change**: `parse_error()` status-code mapping without updating all error subclasses and tests
129
+
-**Do not change**: Pydantic `Field(alias=...)` values — they match the daemon's JSON contract
130
+
-**Safe to extend**: add new error subclasses by adding to `_STATUS_TO_ERROR` dict and creating the class
131
+
-**Safe to extend**: add new client methods by following the `_do()` + `with_retry_sync()` pattern
132
+
-**When adding streaming endpoints**: follow the `chat_stream` pattern — build request, send with `stream=True`, check status before returning `StreamReader`
0 commit comments