* fix(lint): organize imports in queen_orchestrator.create_queen Ruff I001 blocks CI on every PR against main. The deferred imports inside create_queen were not in alphabetical order between the queen package and the framework package; ruff auto-fix moves framework.config below the framework.agents.queen.nodes block. No behavior change. * fix(ci): install Playwright Chromium before Test Tools job The new chart_tools smoke tests added infeabf327require a Chromium build for ECharts/Mermaid rendering, but the test-tools workflow only ran `uv sync` and went straight to pytest. Three tests (test_render_echarts_bar_chart, test_render_echarts_accepts_string_spec, test_render_mermaid_flowchart) crash on every PR with: BrowserType.launch: Executable doesn't exist at /home/runner/.cache/ms-playwright/chromium_headless_shell-1208/... Split the install/run into separate steps and add `playwright install chromium` before pytest. Use `--with-deps` on Linux to pull system libraries; Windows runners only need the browser binary. * fix(tests): adapt test_file_state_cache to new file_ops API The file_ops rewrite infeabf327dropped the standalone hashline_edit tool (the file_system_toolkits/hashline_edit/ directory was removed) and switched edit_file to a mode-first signature (mode, path, old_string, new_string, ...). The test fixture still tried to look up "hashline_edit" via the MCP tool manager and crashed with KeyError before any test could run, and the edit_file calls were positional in the old order so they hit "unknown mode 'e.py'" once the fixture was fixed. Drop the stale hashline_edit lookup and pass mode="replace" explicitly to every edit_file call. All 11 tests pass locally. * fix(tests): skip terminal_tools tests on Windows (POSIX-only) The new terminal_tools package added infeabf327imports the Unix-only `resource` module in tools/src/terminal_tools/common/limits.py to set RLIMIT_CPU / RLIMIT_AS / RLIMIT_FSIZE on subprocesses. Five of the six terminal_tools test files therefore crash on windows-latest with `ModuleNotFoundError: No module named 'resource'` once their fixtures trigger the import chain. test_terminal_tools_pty.py already has the right module-level skip (PTY is POSIX-only). Apply the same `pytestmark = skipif(win32)` to the other five so the whole suite skips cleanly on Windows. The terminal-tools package is bash-only by design (zsh refused at the shell-resolver level), so a Windows port is out of scope.
Hive Server
HTTP API backend for the Hive agent framework. Built on aiohttp, fully async, serving the frontend workspace and external clients.
Architecture
Sessions are the primary entity. A session owns an EventBus + LLM and always has a queen executor. Graphs are optional and can be loaded into and unloaded from a session at any time.
Session {
event_bus # owned by session, shared with queen + graph
llm # owned by session
queen_executor # always present
graph_runtime? # optional — loaded/unloaded independently
}
Structure
server/
├── app.py # Application factory, middleware, static serving
├── session_manager.py # Session lifecycle (create/load graph/unload/stop)
├── sse.py # Server-Sent Events helper
├── routes_sessions.py # Session lifecycle, info, and discovery
├── routes_execution.py # Trigger, inject, chat, stop, resume, replay
├── routes_events.py # SSE event streaming
├── routes_graphs.py # Graph topology & node inspection
├── routes_logs.py # Execution logs (summary/details/tools)
├── routes_credentials.py # Credential management & validation
├── routes_agents.py # Legacy backward-compat routes
└── tests/
└── test_api.py # Full test suite with mocked runtimes
Core Components
app.py — Application Factory
create_app(model) builds the aiohttp Application with:
- CORS middleware — allows localhost origins
- Error middleware — catches exceptions, returns JSON errors
- Static serving — serves the frontend SPA with index.html fallback
- Graceful shutdown — stops all sessions on exit
session_manager.py — Session Lifecycle Manager
Manages Session objects. Key methods:
create_session()— creates EventBus + LLM, starts queen (no graph)create_session_with_worker_graph()— one-step: session + graph + judgeload_graph()— loads agent into existing session, starts judgeunload_graph()— removes graph + judge, queen stays alivestop_session()— tears down everything (graph + queen)
Three-conversation model:
- Queen — persistent interactive executor for user chat (always present)
- Worker —
AgentRuntimethat executes graphs (optional) - Judge — timer-driven background executor for health monitoring (active when a graph is loaded)
sse.py — SSE Helper
Thin wrapper around aiohttp.StreamResponse for Server-Sent Events with keepalive pings.
API Reference
All session-scoped routes use the session_id returned from POST /api/sessions.
Discovery
| Method | Route | Description |
|---|---|---|
GET |
/api/discover |
Discover agents from filesystem |
Returns agents grouped by category with metadata (name, description, node count, tags, etc.).
Session Lifecycle
| Method | Route | Description |
|---|---|---|
POST |
/api/sessions |
Create a session |
GET |
/api/sessions |
List all active sessions |
GET |
/api/sessions/{session_id} |
Session detail (includes entry points + graphs if a graph is loaded) |
DELETE |
/api/sessions/{session_id} |
Stop session entirely |
Create session has two modes:
// Queen-only session (no graph)
POST /api/sessions
{}
// or with custom ID:
{ "session_id": "my-custom-id" }
// Session with graph (one-step)
POST /api/sessions
{
"agent_path": "exports/my-agent",
"agent_id": "custom-graph-name", // optional
"model": "claude-sonnet-4-20250514" // optional
}
- Returns
201with session object on success - Returns
409with{"loading": true}if agent is currently loading - Returns
404if agent_path doesn't exist
Get session returns 202 with {"loading": true} while loading, 404 if not found.
Graph Lifecycle
| Method | Route | Description |
|---|---|---|
POST |
/api/sessions/{session_id}/graph |
Load a graph into session |
DELETE |
/api/sessions/{session_id}/graph |
Unload graph (queen stays alive) |
// Load graph into existing session
POST /api/sessions/{session_id}/graph
{
"agent_path": "exports/my-agent",
"graph_id": "custom-name", // optional
"model": "..." // optional
}
// Unload graph
DELETE /api/sessions/{session_id}/graph
Execution Control
| Method | Route | Description |
|---|---|---|
POST |
/api/sessions/{session_id}/trigger |
Start a new execution |
POST |
/api/sessions/{session_id}/inject |
Inject input into a waiting node |
POST |
/api/sessions/{session_id}/chat |
Smart chat routing |
POST |
/api/sessions/{session_id}/stop |
Cancel a running execution |
POST |
/api/sessions/{session_id}/pause |
Alias for stop |
POST |
/api/sessions/{session_id}/resume |
Resume a paused execution |
POST |
/api/sessions/{session_id}/replay |
Re-run from a checkpoint |
GET |
/api/sessions/{session_id}/goal-progress |
Evaluate goal progress |
Trigger:
POST /api/sessions/{session_id}/trigger
{
"entry_point_id": "default",
"input_data": { "query": "research topic X" },
"session_state": {} // optional
}
// Returns: { "execution_id": "..." }
Chat always delivers messages to the queen conversation.
Worker-originated questions are still shown in the UI, but the user's reply
is mediated by the queen, which can then relay it to the blocked worker via
inject_message() when appropriate.
POST /api/sessions/{session_id}/chat
{ "message": "hello" }
// Returns: { "status": "injected"|"queen", "delivered": true }
Inject into a specific node:
POST /api/sessions/{session_id}/inject
{ "node_id": "gather_info", "content": "user response", "graph_id": "main" }
Stop:
POST /api/sessions/{session_id}/stop
{ "execution_id": "..." }
Resume:
POST /api/sessions/{session_id}/resume
{
"session_id": "session_20260224_...", // worker session to resume
"checkpoint_id": "cp_..." // optional — resumes from latest if omitted
}
Replay (re-run from checkpoint):
POST /api/sessions/{session_id}/replay
{
"session_id": "session_20260224_...",
"checkpoint_id": "cp_..." // required
}
SSE Event Streaming
| Method | Route | Description |
|---|---|---|
GET |
/api/sessions/{session_id}/events |
SSE event stream |
GET /api/sessions/{session_id}/events
GET /api/sessions/{session_id}/events?types=CLIENT_OUTPUT_DELTA,EXECUTION_COMPLETED
Keepalive ping every 15s. Streams from the session's EventBus (covers both queen and worker events).
Default event types: CLIENT_OUTPUT_DELTA, CLIENT_INPUT_REQUESTED, LLM_TEXT_DELTA, TOOL_CALL_STARTED, TOOL_CALL_COMPLETED, EXECUTION_STARTED, EXECUTION_COMPLETED, EXECUTION_FAILED, EXECUTION_PAUSED, NODE_LOOP_STARTED, NODE_LOOP_ITERATION, NODE_LOOP_COMPLETED, NODE_ACTION_PLAN, EDGE_TRAVERSED, GOAL_PROGRESS, NODE_INTERNAL_OUTPUT, NODE_STALLED, NODE_RETRY, NODE_TOOL_DOOM_LOOP, CONTEXT_COMPACTED, WORKER_GRAPH_LOADED.
Session Info
| Method | Route | Description |
|---|---|---|
GET |
/api/sessions/{session_id}/stats |
Runtime statistics |
GET |
/api/sessions/{session_id}/entry-points |
List entry points |
GET |
/api/sessions/{session_id}/graphs |
List loaded graph IDs |
Graph & Node Inspection
| Method | Route | Description |
|---|---|---|
GET |
/api/sessions/{session_id}/graphs/{graph_id}/nodes |
List nodes + edges |
GET |
/api/sessions/{session_id}/graphs/{graph_id}/nodes/{node_id} |
Node detail + outgoing edges |
GET |
/api/sessions/{session_id}/graphs/{graph_id}/nodes/{node_id}/criteria |
Success criteria + last execution info |
GET |
/api/sessions/{session_id}/graphs/{graph_id}/nodes/{node_id}/tools |
Resolved tool metadata |
List nodes supports optional enrichment with session progress:
GET /api/sessions/{session_id}/graphs/{graph_id}/nodes?session_id=worker_session_id
Adds visit_count, has_failures, is_current, in_path to each node.
Logs
| Method | Route | Description |
|---|---|---|
GET |
/api/sessions/{session_id}/logs |
Session-level logs |
GET |
/api/sessions/{session_id}/graphs/{graph_id}/nodes/{node_id}/logs |
Node-scoped logs |
# List recent runs
GET /api/sessions/{session_id}/logs?level=summary&limit=20
# Detailed per-node execution for a specific worker session
GET /api/sessions/{session_id}/logs?session_id=ws_id&level=details
# Tool call logs
GET /api/sessions/{session_id}/logs?session_id=ws_id&level=tools
# Node-scoped (requires session_id query param)
GET .../nodes/{node_id}/logs?session_id=ws_id&level=all
Log levels: summary (run stats), details (per-node execution), tools (tool calls + LLM text).
Credentials
| Method | Route | Description |
|---|---|---|
GET |
/api/credentials |
List credential metadata (no secrets) |
POST |
/api/credentials |
Save a credential |
GET |
/api/credentials/{credential_id} |
Get credential metadata |
DELETE |
/api/credentials/{credential_id} |
Delete a credential |
POST |
/api/credentials/check-agent |
Validate agent credentials |
Save credential:
POST /api/credentials
{ "credential_id": "brave_search", "keys": { "api_key": "BSA..." } }
Check agent credentials — two-phase validation (same as runtime startup):
POST /api/credentials/check-agent
{
"agent_path": "exports/my-agent",
"verify": true // optional, default true — run health checks
}
// Returns:
{
"required": [
{
"credential_name": "brave_search",
"credential_id": "brave_search",
"env_var": "BRAVE_SEARCH_API_KEY",
"description": "Brave Search API key",
"help_url": "https://...",
"tools": ["brave_web_search"],
"node_types": [],
"available": true,
"valid": true, // true/false/null (null = not checked)
"validation_message": "OK", // human-readable health check result
"direct_api_key_supported": true,
"aden_supported": true,
"credential_key": "api_key"
}
]
}
When verify: true, runs health checks (lightweight HTTP calls) against each available credential to confirm it actually works — not just that it exists.
Key Patterns
- Session-primary — sessions are the lookup key for all routes, workers are optional children
- Per-request manager access — routes get
SessionManagerviarequest.app["manager"] - Path validation — user-provided path segments validated with
safe_path_segment()to prevent directory traversal - Event-driven streaming — per-client buffer queues (max 1000 events) with 15s keepalive pings
- Shared EventBus — session owns the bus, queen and worker both publish to it, SSE always connects to
session.event_bus - No secrets in responses — credential endpoints never return secret values
Storage Paths
~/.hive/
├── queen/session/{session_id}/ # Queen conversation state
├── judge/session/{session_id}/ # Judge state
├── agents/{agent_name}/sessions/ # Worker execution sessions
└── credentials/ # Encrypted credential store
Running Tests
pytest framework/server/tests/ -v