Files
Hundao 8cb0531959 fix(ci): unblock main CI, sort imports + install Playwright Chromium (#7172)
* 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 in feabf327 require 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 in feabf327 dropped 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 in feabf327 imports 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.
2026-05-05 00:32:59 +08:00
..
2026-05-01 13:35:04 -07:00
2026-02-20 18:01:51 -08:00
2026-04-29 11:25:29 -07:00
2026-04-28 10:43:54 -07:00
2026-04-29 19:16:00 -07:00
2026-04-27 18:24:18 -07:00
2026-04-20 11:19:57 -07:00
2026-04-21 17:20:54 -07:00
2026-04-17 21:01:18 -07:00
2026-04-22 21:33:33 -07:00
2026-04-28 18:25:21 -07:00
2026-04-26 19:45:52 -07:00
2026-04-29 19:16:00 -07:00
2026-05-03 11:45:37 -07:00
2026-02-20 18:01:51 -08:00

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 + judge
  • load_graph() — loads agent into existing session, starts judge
  • unload_graph() — removes graph + judge, queen stays alive
  • stop_session() — tears down everything (graph + queen)

Three-conversation model:

  1. Queen — persistent interactive executor for user chat (always present)
  2. WorkerAgentRuntime that executes graphs (optional)
  3. 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 201 with session object on success
  • Returns 409 with {"loading": true} if agent is currently loading
  • Returns 404 if 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 SessionManager via request.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