|
1 | | -# AGENTS.md — hawk-sdk-python |
| 1 | +--- |
| 2 | +description: Extending hawk-eco — how to write AGENTS.md files, custom specialists, skills, hooks, MCP servers, and plugins. |
| 3 | +globs: "*.go, *.js, *.md, *.json, *.toml, *.yaml, *.yml" |
| 4 | +alwaysApply: false |
| 5 | +--- |
2 | 6 |
|
3 | | -Python SDK for the Hawk daemon API. Provides an idiomatic Python client for chat, streaming, sessions, and stats. |
| 7 | +# Extending hawk-eco |
4 | 8 |
|
5 | | -## Design Principles |
| 9 | +hawk-eco is an open-source code intelligence platform. This document describes how to extend it with custom tools, skills, hooks, and integrations. |
6 | 10 |
|
7 | | -- **Thin wrapper** — maps directly to the hawk daemon HTTP API |
8 | | -- **Type-hinted** — full type annotations for IDE support |
9 | | -- **Minimal dependencies** — `httpx` (HTTP), `pydantic` v2 (models), and |
10 | | - `eval-type-backport` (only on Python < 3.10) |
| 11 | +## 1. Drop a project `AGENTS.md` |
11 | 12 |
|
12 | | -## Build & Test |
| 13 | +When hawk-eco starts in a directory, it looks for project-level instructions and injects them into the system prompt. The lookup walks from your current working directory **up to the nearest git root** and reads the first matching file at each level — general rules at the repo root, more specific rules in sub-trees. Files are labeled with their directory in the prompt (e.g. `## Project guidelines (services/api/AGENTS.md)`). |
| 14 | + |
| 15 | +Accepted file names, in priority order at each level: |
| 16 | + |
| 17 | +| Path | Notes | |
| 18 | +| --- | --- | |
| 19 | +| `./AGENTS.md` | The classic spot — committed to your repo, shared with the team. | |
| 20 | +| `./ZERO.md` | Brand-specific alias. Same format, lower priority. | |
| 21 | +| `./.zero/AGENTS.md` | Project-local, hidden, gitignored. Personal notes that stay out of git. | |
| 22 | + |
| 23 | +Matching is **case-insensitive** on the basename, so `AGENTS.md`, `Agents.md`, and `agents.md` resolve to the same file on Windows and macOS. The git-tracked filename in this repo is `AGENTS.md` — keep that on case-sensitive filesystems (Linux, the WSL filesystem, or a CI runner) to match what the loader looks for. |
| 24 | + |
| 25 | +Both files use the same format. YAML frontmatter is optional; the markdown body is loaded as instructions for the agent. hawk-eco reads the file once at session start, so changes take effect on the next launch — not mid-session. |
| 26 | + |
| 27 | +```markdown |
| 28 | +# Project conventions for <your project> |
| 29 | + |
| 30 | +- Build with `make`, not `go build` directly. |
| 31 | +- Tests live next to the source file (`foo_test.go` next to `foo.go`). |
| 32 | +- Run `make lint` before opening a PR. |
| 33 | +- Never edit files under `third_party/` — those are vendored. |
| 34 | +``` |
| 35 | + |
| 36 | +Tips: |
| 37 | + |
| 38 | +- Keep each file under ~8 KiB. hawk-eco caps the **total** across all matched files at 32 KiB; everything past the cap is dropped. |
| 39 | +- Re-state rules in the imperative voice: "Run `make lint`", not "you should consider running the linter". |
| 40 | +- Don't put secrets, model IDs, or environment-specific paths in `AGENTS.md`. Use config files for those. |
| 41 | +- In a monorepo, drop a narrower `AGENTS.md` in each sub-tree (e.g. `services/api/AGENTS.md`). hawk-eco picks those up automatically when you launch from inside the sub-tree. |
| 42 | +- A YAML frontmatter block (`---\n...\n---`) at the top is preserved verbatim in the injected prompt but is not parsed for `globs:` or `alwaysApply:` scoping today — keep the body self-contained. |
| 43 | + |
| 44 | +### Personal guidelines, across every project |
| 45 | + |
| 46 | +For preferences that follow *you*, not a specific repo (tone, tooling habits, workflow), drop a `ZERO.md` in your user config directory: `~/.config/hawk-eco/ZERO.md` on Linux/macOS, `%AppData%\Roaming\hawk-eco\ZERO.md` on Windows — the same directory as config files and your personal specialists. Same format and 8 KiB cap as the project files above, and the same case-insensitive basename match. |
| 47 | + |
| 48 | +This file is injected as its own `## User guidelines` section, before the project's `AGENTS.md`/`ZERO.md`, and is labeled as personal preference in the prompt: project guidelines are the later, more specific instruction and take precedence over it when the two conflict. |
| 49 | + |
| 50 | +## 2. Custom specialists |
| 51 | + |
| 52 | +Specialists are hawk-eco's sub-agents. Three scopes, in priority order: |
| 53 | + |
| 54 | +| Scope | Path | Shared? | |
| 55 | +| --- | --- | --- | |
| 56 | +| Built-in | compiled into hawk-eco | yes | |
| 57 | +| User | `~/.config/hawk-eco/specialists/*.md` | no — your machine only | |
| 58 | +| Project | `./.zero/specialists/*.md` | yes — the repo team | |
| 59 | + |
| 60 | +Project overrides user overrides built-in when names collide. |
| 61 | + |
| 62 | +A specialist is a markdown manifest with frontmatter and a system prompt: |
| 63 | + |
| 64 | +```markdown |
| 65 | +--- |
| 66 | +description: Reviews API changes for breaking-change risk and missing tests. |
| 67 | +tools: read-only,plan |
| 68 | +--- |
| 69 | + |
| 70 | +You review API changes. For every changed hunk in `internal/api/` or any file |
| 71 | +that ends in `_api.go`: |
| 72 | + |
| 73 | +1. Confirm the public signature is backward-compatible, or note the breaking |
| 74 | + change explicitly with the migration path. |
| 75 | +2. Confirm a corresponding test exists in `internal/api/*_test.go` and that |
| 76 | + the new behaviour is exercised. |
| 77 | +3. Flag any new exported symbol without a doc comment. |
| 78 | + |
| 79 | +Reply with one JSON object per finding: `{"file", "line", "severity", "message", "fix"}`. |
| 80 | +``` |
| 81 | + |
| 82 | +CLI management: |
| 83 | + |
| 84 | +```bash |
| 85 | +hawk-eco specialist list |
| 86 | +hawk-eco specialist show api-reviewer |
| 87 | +hawk-eco specialist create api-reviewer \ |
| 88 | + --project \ |
| 89 | + --description "Reviews API changes" \ |
| 90 | + --tools read-only,plan \ |
| 91 | + --prompt "$(cat api-reviewer.md)" |
| 92 | +hawk-eco specialist edit api-reviewer --project |
| 93 | +hawk-eco specialist delete api-reviewer --project |
| 94 | +hawk-eco specialist path # prints the resolved specialists directory |
| 95 | +``` |
| 96 | + |
| 97 | +## 3. Skills |
| 98 | + |
| 99 | +Skills are markdown instruction files that extend agent capabilities. They can be: |
| 100 | +- Project-scoped: dropped in `./.zero/skills/` or `./skills/` |
| 101 | +- User-scoped: dropped in `~/.config/hawk-eco/skills/` |
| 102 | + |
| 103 | +A skill manifest: |
| 104 | + |
| 105 | +```markdown |
| 106 | +--- |
| 107 | +description: How to review Go code for security issues |
| 108 | +globs: "*.go" |
| 109 | +alwaysApply: true |
| 110 | +--- |
| 111 | + |
| 112 | +When reviewing Go code for security: |
| 113 | + |
| 114 | +1. Check for SQL injection patterns |
| 115 | +2. Verify error handling doesn't expose sensitive data |
| 116 | +3. Confirm secrets are not hardcoded |
| 117 | +4. Validate input sanitization |
| 118 | +``` |
| 119 | + |
| 120 | +## 4. Hooks |
| 121 | + |
| 122 | +Hooks allow custom commands to run at specific lifecycle points: |
| 123 | +- `beforeReview` — runs before code review starts |
| 124 | +- `afterReview` — runs after code review completes |
| 125 | +- `sessionStart` — runs at session initialization |
| 126 | +- `sessionEnd` — runs at session teardown |
13 | 127 |
|
14 | 128 | ```bash |
15 | | -pip install -e ".[dev]" # Install with dev deps |
16 | | -pytest # Run tests |
17 | | -pytest --cov=hawk --cov-report=term-missing # Coverage |
18 | | -ruff check . # Lint |
19 | | -ruff format . # Format |
20 | | -mypy . # Type check |
| 129 | +hawk-eco hook add beforeReview --command "lint-check" |
| 130 | +hawk-eco hook remove beforeReview |
| 131 | +hawk-eco hook list |
21 | 132 | ``` |
22 | 133 |
|
23 | | -## Architecture |
24 | | - |
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 |
31 | | - |
32 | | -## Conventions |
33 | | - |
34 | | -- Python 3.9+ |
35 | | -- `ruff` for linting and formatting (enforced in CI) |
36 | | -- Type annotations required on all public APIs |
37 | | -- Conventional Commits: `feat:`, `fix:`, `docs:`, `refactor:`, `test:` |
38 | | -- No `Co-authored-by:` trailers |
39 | | -- `pytest` for testing, `respx` for HTTP mocking |
40 | | - |
41 | | -## Common Pitfalls |
42 | | - |
43 | | -- `HawkClient` uses context manager (`with` statement) for cleanup |
44 | | -- Discovery scans localhost ports — tests must mock this |
45 | | -- `Retry-After: 0` is valid — respect it, don't retry immediately |
46 | | - |
47 | | -## Naming Conventions |
48 | | - |
49 | | -- **Modules**: snake_case, one concept per file (`client.py`, `errors.py`, `retry.py`, `streaming.py`) |
50 | | -- **Classes**: PascalCase, noun-based (`HawkClient`, `AsyncHawkClient`, `StreamReader`, `RetryConfig`) |
51 | | -- **Error classes**: suffix with `Error` (`HawkAPIError`, `NotFoundError`, `RateLimitError`, `InternalServerError`) |
52 | | -- **Private methods**: leading underscore (`_request`, `_build_headers`, `_validate_base_url`, `_compute_backoff`) |
53 | | -- **Type aliases**: use `TypeVar` for generics (`T = TypeVar("T")` in `types.py` and `retry.py`) |
54 | | -- **Pydantic fields**: use `Field(alias="snake_case")` for API-mapped names (`session_id`, `tokens_in`, `active_sessions`) |
55 | | -- **Test classes**: `Test` prefix + subject (`TestHawkClientSync`, `TestAsyncHawkClient`, `TestParseError`, `TestRetryConfig`) |
56 | | -- **Test methods**: `test_` + behavior (`test_health`, `test_chat_stream_error`, `test_no_retry_on_404`) |
57 | | - |
58 | | -## API Patterns |
59 | | - |
60 | | -### Pydantic Models |
61 | | -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: |
62 | | -```python |
63 | | -class ChatResponse(BaseModel): |
64 | | - session_id: str = Field(alias="session_id") |
65 | | - tokens_in: int = Field(alias="tokens_in") |
66 | | - model_config = {"populate_by_name": True} |
| 134 | +## 5. MCP integration |
| 135 | + |
| 136 | +MCP (Model Context Protocol) servers can expose tools to hawk-eco: |
| 137 | + |
| 138 | +```bash |
| 139 | +hawk-eco mcp add --name server --url http://localhost:8080 |
| 140 | +hawk-eco mcp remove server |
| 141 | +hawk-eco mcp list |
67 | 142 | ``` |
68 | 143 |
|
69 | | -### Error Hierarchy |
70 | | -`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. |
| 144 | +## 6. Plugins |
71 | 145 |
|
72 | | -### Retry Pattern |
73 | | -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). |
| 146 | +Plugins extend hawk-eco with custom tools and capabilities: |
74 | 147 |
|
75 | | -### Streaming |
76 | | -`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. |
| 148 | +```bash |
| 149 | +hawk-eco plugin add --name my-plugin --path ./my-plugin |
| 150 | +hawk-eco plugin remove my-plugin |
| 151 | +hawk-eco plugin list |
| 152 | +``` |
77 | 153 |
|
78 | | -### Dual Client Pattern |
79 | | -`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`). |
| 154 | +## 7. Verification |
80 | 155 |
|
81 | | -## Testing Patterns |
| 156 | +hawk-eco includes a self-verification system to validate local changes before contributing: |
82 | 157 |
|
83 | | -### HTTP Mocking |
84 | | -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"`: |
85 | | -```python |
86 | | -def test_health(self, respx_mock: respx.MockRouter) -> None: |
87 | | - respx_mock.get(f"{BASE_URL}/v1/health").mock( |
88 | | - return_value=httpx.Response(200, json={...}) |
89 | | - ) |
| 158 | +```bash |
| 159 | +hawk-eco verify |
| 160 | +hawk-eco verify --fix |
90 | 161 | ``` |
91 | 162 |
|
92 | | -### Async Tests |
93 | | -Async test classes use `@pytest.mark.asyncio` decorator. The `respx_mock` fixture works for both sync and async. |
94 | | - |
95 | | -### Error Tests |
96 | | -`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`). |
97 | | - |
98 | | -### Streaming Tests |
99 | | -SSE body strings follow the format `"event: content\ndata: Hello\n\n"`. Tests verify both `collect_text()` and mid-stream `break` behavior. |
100 | | - |
101 | | -### Retry Tests |
102 | | -`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. |
103 | | - |
104 | | -## Key File Locations |
105 | | - |
106 | | -| What | Where | |
107 | | -|------|-------| |
108 | | -| Public API exports | `src/hawk/__init__.py` | |
109 | | -| Client (sync + async) | `src/hawk/client.py` | |
110 | | -| Pydantic models | `src/hawk/types.py` | |
111 | | -| Error types + parser | `src/hawk/errors.py` | |
112 | | -| Retry logic | `src/hawk/retry.py` | |
113 | | -| SSE streaming | `src/hawk/streaming.py` | |
114 | | -| Agent abstraction | `src/hawk/agent.py` | |
115 | | -| Tool decorator + loop | `src/hawk/tools.py` | |
116 | | -| Workflow engine | `src/hawk/workflow.py` | |
117 | | -| Agent discovery | `src/hawk/discovery.py` | |
118 | | -| Memory graph ops | `src/hawk/memory_tools.py` | |
119 | | -| Evaluation/benchmarks | `src/hawk/evaluate.py` | |
120 | | -| Tracing/observability | `src/hawk/tracing.py` | |
121 | | -| Client tests | `tests/test_client.py` | |
122 | | -| Error tests | `tests/test_errors.py` | |
123 | | -| Retry tests | `tests/test_retry.py` | |
124 | | -| Streaming tests | `tests/test_streaming.py` | |
125 | | - |
126 | | -## Refactoring Guidelines |
127 | | - |
128 | | -- **Safe to refactor**: internal helpers like `_build_headers`, `_validate_base_url`, `_compute_backoff` — they are private and well-tested |
129 | | -- **Do not change**: `parse_error()` status-code mapping without updating all error subclasses and tests |
130 | | -- **Do not change**: Pydantic `Field(alias=...)` values — they match the daemon's JSON contract |
131 | | -- **Safe to extend**: add new error subclasses by adding to `_STATUS_TO_ERROR` dict and creating the class |
132 | | -- **Safe to extend**: add new client methods by following the `_do()` + `with_retry_sync()` pattern |
133 | | -- **When adding streaming endpoints**: follow the `chat_stream` pattern — build request, send with `stream=True`, check status before returning `StreamReader` |
| 163 | +## Development |
| 164 | + |
| 165 | +```bash |
| 166 | +make lint |
| 167 | +hawk-eco verify |
| 168 | +``` |
0 commit comments