mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
The default surface was 34 tools, 61 KB, ~15,300 tokens of schema in every
prompt of every agent. The mode system already names what a session should
carry, but only the tools register_all_tools enumerates obeyed it: zsh,
curl, playwright and the helpers (ps, npx, uvx, jq, wget, cdp, open) stayed
on in every mode. A mode now decides every discovered tool, the `hanzo`
mode lists what a coding agent calls every few steps (18 tools, 32 KB,
~8,000 tokens), and the rest is a name away: HANZO_MCP_ENABLED_TOOLS=a,b
or =all, `tool enable`, or `mode`.
hanzo-tools-shell 0.6.6: run_shell kept every byte a foreground command
wrote until the command ended. `yes` for thirty seconds is gigabytes. It
keeps the first 16 MiB per stream and drains the rest.
The tests' session-wide patch of asyncio.create_subprocess_exec handed
every shell command a stream that never reached EOF; the first test to run
one read it in a loop until the machine was out of memory (22 GB, killed).
The patch is now opt-in and its process has exited.
bench/latency.py measures the cold start, the tools/list bill and the common
calls through a real MCP client over stdio. Interleaved rounds, wall and CPU
seconds, p50 over 6 cold starts:
cold wall cold CPU tools bytes ~tokens
installed 0.15.18 9.34 s 10.46 s 30 52,601 13,150
main 0.15.20 1.52 s 1.13 s 34 61,287 15,321
this 0.96 s 1.06 s 18 32,222 8,055
The installed environment's 9 s is its hanzoai 8.5.156, whose package init
imports the whole generated cloud client; hanzo-mcp >= 0.15.20 requires
hanzoai >= 8.5.624, which loads it lazily. Per-call latency is spawn-bound
(zsh 17.6 ms, git status 33 ms, fs.read 2.5 ms at p50) and moves with the
machine's load; no call path changed.
62 lines
2.3 KiB
Python
62 lines
2.3 KiB
Python
"""The default surface is what every prompt of an agent carries: tools/list is its schema bill.
|
|
|
|
The active mode is the whole manifest. A tool it does not list is off, whichever package ships it (before,
|
|
only the names `register_all_tools` enumerated obeyed the mode, so the shell and browser helpers stayed on
|
|
in every mode); what the user asks for by name stands over the mode.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
|
|
import pytest
|
|
from mcp.server.fastmcp import FastMCP
|
|
|
|
from hanzo_mcp.tools import register_all_tools
|
|
from hanzo_mcp.tools.common.permissions import PermissionManager
|
|
from hanzo_mcp.tools.common.mode import ModeRegistry
|
|
from hanzo_mcp.tools.common.mode_loader import ModeLoader
|
|
from hanzo_mcp.tools.common.personality import DEFAULT_TOOLS
|
|
|
|
# Tools registered outside any mode: the way to a wider surface.
|
|
SYSTEM = {"tool", "version", "stats", "mode"}
|
|
BUDGET_BYTES = 36_000 # the default surface at 0.15.20 was 61,000
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _isolated(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("HOME", str(tmp_path))
|
|
monkeypatch.delenv("HANZO_MCP_ENABLED_TOOLS", raising=False)
|
|
monkeypatch.delenv("HANZO_MCP_MODE", raising=False)
|
|
ModeRegistry.clear() if hasattr(ModeRegistry, "clear") else None
|
|
ModeLoader.initialize_modes()
|
|
|
|
|
|
def listed():
|
|
server = FastMCP("surface")
|
|
register_all_tools(server, PermissionManager())
|
|
return asyncio.run(server.list_tools())
|
|
|
|
|
|
def test_the_default_lists_the_default_mode_and_nothing_else():
|
|
names = {t.name for t in listed()}
|
|
assert names <= set(DEFAULT_TOOLS) | SYSTEM, sorted(names - set(DEFAULT_TOOLS) - SYSTEM)
|
|
assert {"fs", "zsh", "git", "browser", "playwright"} <= names
|
|
|
|
|
|
def test_the_default_stays_inside_its_schema_budget():
|
|
size = sum(len(json.dumps(t.model_dump(exclude_none=True))) for t in listed())
|
|
assert size < BUDGET_BYTES, size
|
|
|
|
|
|
def test_a_name_asked_for_stands_over_the_mode(monkeypatch):
|
|
assert "memory" not in {t.name for t in listed()}
|
|
monkeypatch.setenv("HANZO_MCP_ENABLED_TOOLS", "memory,critic")
|
|
assert {"memory", "critic"} <= {t.name for t in listed()}
|
|
|
|
|
|
def test_all_restores_every_tool(monkeypatch):
|
|
base = {t.name for t in listed()}
|
|
monkeypatch.setenv("HANZO_MCP_ENABLED_TOOLS", "all")
|
|
names = {t.name for t in listed()}
|
|
assert base < names and {"memory", "computer", "wget"} <= names
|