Skip to content

Repository files navigation

Hawk SDK for Python

Official Python client for the Hawk daemon API

PythonLicenseCI


The Hawk SDK for Python is the official client for interacting with the Hawk daemon API. It provides an idiomatic Python interface for chat, streaming, sessions, stats, and agent orchestration. Both synchronous and async clients are provided, with the same API surface on each.

Features

  • Dual client pattern - HawkClient (sync) and AsyncHawkClient with identical method signatures
  • Streaming - SSE-based real-time chat streaming via StreamReader / AsyncStreamReader
  • Type safety - Pydantic v2 models with full type hints (Python 3.9+)
  • Context managers - with HawkClient() as client: for automatic resource cleanup
  • Typed errors - Status-code-based exception hierarchy (AuthenticationError, NotFoundError, RateLimitError, etc.)
  • Retry with backoff - Configurable exponential backoff with jitter via RetryConfig
  • Minimal dependencies - httpx (HTTP), pydantic v2, and eval-type-backport (Python < 3.10)
  • Agent orchestration - Agent / AsyncAgent with conversation memory, planning, and tool loops
  • Toolkit - Toolkit for grouping and managing collections of tools
  • Tracing - OpenTelemetry-compatible tracing with trace, trace_chat, and trace_tool
  • Workflow engine - Workflow / AsyncWorkflow for multi-step agent pipelines
  • Planning - Plan / PlanNotebook for task breakdown and tracking
  • Evaluation - EvalTask and run_benchmark for benchmarking agent performance

Installation

⚠️ Pre-release — not yet published to PyPI.

hawk-sdk is under active development and has not yet been published to the Python Package Index. Installing from PyPI will fail until a stable release is tagged. In the meantime, install directly from source:

git clone https://github.com/GrayCodeAI/hawk-sdk-python.git
cd hawk-sdk-python
pip install -e .

Once the first stable release is tagged, the standard install will work:

pip install hawk-sdk # available after first stable release

Quick Start

Synchronous

fromhawkimportHawkClientwithHawkClient() asclient:
# Health checkhealth=client.health()
print(f"Status: {health.status}, Version: {health.version}")
print(f"Active sessions: {health.active_sessions}")
# Chatresponse=client.chat("Explain decorators in Python")
print(response.response)
print(f"Tokens: in={response.tokens_in}, out={response.tokens_out}")

Asynchronous

importasynciofromhawkimportAsyncHawkClientasyncdefmain():
asyncwithAsyncHawkClient() asclient:
health=awaitclient.health()
print(f"Status: {health.status}, Version: {health.version}")
response=awaitclient.chat("Hello!")
print(response.response)
asyncio.run(main())

Streaming

fromhawkimportHawkClientwithHawkClient() asclient:
withclient.chat_stream("Write a haiku about coding") asstream:
foreventinstream.events():
ifevent.eventisNoneorevent.event=="content":
print(event.data, end="", flush=True)
print()
# Or collect all text at oncewithclient.chat_stream("Write a haiku about coding") asstream:
text=stream.collect_text()
print(f"Full response: {text}")
# Or collect tool callswithclient.chat_stream("What tools do I have?", tools=[{"type": "list"}]) asstream:
calls=stream.collect_tool_calls()
forcallincalls:
print(f"Tool: {call.name}({call.arguments})")

API Reference

HawkClient / AsyncHawkClient

Both clients share the same constructor and method signatures.

Constructor

HawkClient(
base_url: str="http://127.0.0.1:4590",
api_key: str|None=None,
retry_config: RetryConfig|None=None,
timeout: float=30.0,
pool_connections: int=10,
pool_maxsize: int=100,
)
ParameterDefaultDescription
base_urlhttp://127.0.0.1:4590Base URL of the Hawk daemon
api_keyNoneBearer token for authenticated requests
retry_configDEFAULT_RETRY_CONFIGRetry configuration (429, 5xx, etc.)
timeout30.0HTTP request timeout in seconds
pool_connections10Max keep-alive connections
pool_maxsize100Max total connections

Methods

# Healthhealth() ->HealthResponse# Chatchat(
prompt: str,
session_id: str|None=None,
model: str|None=None,
max_turns: int|None=None,
autonomy: str|None=None,
cwd: str|None=None,
agent: str|None=None,
tools: list[dict[str, Any]] |None=None,
tool_results: list[ToolResult] |None=None,
tool_choice: str|None=None,
parallel_tool_calls: bool|None=None,
) ->ChatResponse# Streaming chatchat_stream(
prompt: str,
session_id: str|None=None,
model: str|None=None,
max_turns: int|None=None,
autonomy: str|None=None,
cwd: str|None=None,
agent: str|None=None,
tools: list[dict[str, Any]] |None=None,
tool_results: list[ToolResult] |None=None,
tool_choice: str|None=None,
parallel_tool_calls: bool|None=None,
) ->StreamReader# or AsyncStreamReader# Session managementget_session(session_id: str) ->SessionDetaillist_sessions() ->list[SessionSummary]
delete_session(session_id: str) ->None# Messages with paginationlist_messages(
session_id: str,
limit: int=50,
offset: int=0,
) ->PaginatedResponse[Message]
# Privacy-safe execution graph (same method on sync and async clients)get_graph(
session_id: str,
repository: str|None=None,
trace_checkpoints: list[str] |None=None,
) ->GraphExport# Statsstats() ->StatsResponse

Portable graph models

GraphExport, GraphNode, GraphEdge, and GraphEvent model the shared *.graph/v1 wire contract. Pydantic validates vocabulary, timestamps, provenance, duplicate IDs, and dangling topology at the SDK boundary:

fromhawkimportGraphExportgraph=GraphExport.model_validate(payload)

get_graph() retrieves the authenticated /v1/sessions/{id}/graph projection and validates it with these models. They remain data-only consumer models; the SDK does not own graph facts or storage.

Tool Execution Loop

chat_with_tools (and its async counterpart chat_with_tools_async) implements the tool-use loop: it sends a chat request, checks for tool calls in the response, executes matching tools, appends results to the conversation, and repeats until either no more tool calls are requested or max_rounds is reached.

fromhawkimportHawkClient, Tool, chat_with_toolsdefget_weather(location: str) ->str:
returnf"Sunny in {location}"tools= [
Tool(
name="get_weather",
description="Get the weather",
parameters={
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
fn=get_weather,
)
]
withHawkClient() asclient:
response=chat_with_tools(
client,
prompt="What's the weather in San Francisco?",
tools=tools,
max_rounds=10,
)
print(response.response)

Typed Errors

All API errors inherit from HawkAPIError. Catch specific subclasses:

fromhawkimportHawkClient, HawkAPIError, RateLimitError, AuthenticationErrorwithHawkClient() asclient:
try:
response=client.chat("Hello")
exceptAuthenticationError:
print("Invalid API key")
exceptRateLimitErrorase:
print(f"Rate limited, retry after {e.retry_after}s")
exceptHawkAPIErrorase:
print(f"API error {e.status_code}: {e.message}")

Error classes: BadRequestError (400), AuthenticationError (401), ForbiddenError (403), NotFoundError (404), RateLimitError (429), InternalServerError (500), ServiceUnavailableError (503).

Error Handling

Every public method wraps its logic in a retry closure that automatically:

  • Respects Retry-After headers on 429 responses
  • Uses exponential backoff with jitter for retryable statuses (429, 500, 502, 503, 504)
  • Raises typed exceptions matching the HTTP status code

Ecosystem Boundaries

hawk-sdk-python is a consumer of Hawk public APIs and contracts. It does not import from engine repos such as eyrie, yaad, tok, trace, sight, or inspect. If a capability is needed across repos, it should be exposed through Hawk or hawk-core-contracts, not through engine internals.

Contributing

Contributions are welcome — please read CONTRIBUTING.md before opening a pull request.

License

MIT - see LICENSE for details.

About

Official Python SDK for the Hawk daemon API — sync/async clients, Pydantic v2, SSE streaming, minimal deps.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages