Add Handbook.md benchmark tasks

This commit is contained in:
DerekSurge
2026-06-24 12:44:34 -07:00
commit 72f9a65917
1800 changed files with 323589 additions and 0 deletions
@@ -0,0 +1,330 @@
{
"schema_version": "v1",
"tools": [
{
"name": "echo",
"description": "Echoes a message",
"inputSchema": {
"additionalProperties": false,
"properties": {
"message": {
"description": "The message to echo",
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
}
},
{
"name": "executeBash",
"description": "Execute a bash command in an isolated directory.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"command": {
"description": "The bash command to execute",
"type": "string"
},
"timeout_seconds": {
"anyOf": [
{
"maximum": 1800,
"minimum": 1,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Timeout in seconds (default 120, max 1800)"
}
},
"required": [
"command"
],
"type": "object"
}
},
{
"name": "executePython",
"description": "Execute Python code in an isolated directory.\n\n``executePython`` was dropped from core; this shim reimplements it on core's\nsandbox, piping the source to ``python3 -`` via stdin \u2014 matching the original\nso large scripts aren't capped by the OS command-line length (``python3 -c\n<code>`` would put the whole program in argv and can hit \"Argument list too\nlong\").",
"inputSchema": {
"additionalProperties": false,
"properties": {
"code": {
"description": "The Python code to execute",
"type": "string"
},
"timeout_seconds": {
"anyOf": [
{
"maximum": 1800,
"minimum": 1,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Timeout in seconds (default 120, max 1800)"
}
},
"required": [
"code"
],
"type": "object"
}
},
{
"name": "export_state",
"description": "Export the full core state as JSON.\n\nRound-trips with import_state. State is keyed by snake_case entity-class\nname (e.g. ``project``, ``employee``, ``slack_message``).",
"inputSchema": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
{
"name": "import_state",
"description": "Replace the full core state with the provided JSON.\n\nFor synthetic-data injection and test setup. Round-trips with export_state.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"state": {
"additionalProperties": true,
"description": "Empty state \u2014 core has no DB to snapshot.",
"properties": {},
"type": "object"
}
},
"required": [
"state"
],
"type": "object"
}
},
{
"name": "listFiles",
"description": "List files and subdirectories in a directory inside the sandbox.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"directory": {
"default": ".",
"description": "Directory to list, relative to the sandbox root. Use /workdir/ prefix or an absolute path within the sandbox to be explicit. Defaults to the sandbox root.",
"type": "string"
}
},
"type": "object"
}
},
{
"name": "prepareGradingContext",
"description": "Prepare the text_for_grading string for rubric evaluation.\n\nCollects file evidence from the sandbox, formats as XML,\nand prepends to the agent's final_output.\n\nArgs:\n final_output: The agent's final response text.\n directory: Root directory to search. Defaults to the sandbox directory.\n extensions: Only include files with these extensions (without dot).\n exclude_patterns: Directory/file name patterns to skip.\n max_files: Maximum number of files to include as evidence. Eligible files\n beyond this cap are omitted but reported (logged and listed in the\n XML under <files_omitted_due_to_max_files>) rather than dropped\n silently. Defaults to DEFAULT_MAX_FILES.\n max_content_bytes: Maximum bytes of content to read per file.\n\nReturns:\n The assembled text_for_grading string.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"final_output": {
"type": "string"
},
"directory": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"extensions": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"exclude_patterns": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"max_files": {
"default": 100,
"type": "integer"
},
"max_content_bytes": {
"default": 250000,
"type": "integer"
}
},
"required": [
"final_output"
],
"type": "object"
}
},
{
"name": "readFile",
"description": "Read a file from the sandbox, with optional offset/limit (bytes) for paginating large files.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"file_path": {
"description": "Path to the file. Use /workdir/ prefix for sandbox files, or an absolute path within the sandbox.",
"type": "string"
},
"offset": {
"default": 0,
"description": "Byte offset to start reading from. Use 0 for the beginning or a previous call's next_offset to continue.",
"minimum": 0,
"type": "integer"
},
"limit": {
"anyOf": [
{
"minimum": 1,
"type": "integer"
},
{
"type": "null"
}
],
"default": 200000,
"description": "Maximum number of bytes to read. Defaults to 200000. Pass null to read from offset to end of file."
}
},
"required": [
"file_path"
],
"type": "object"
}
},
{
"name": "readMedia",
"description": "Read an image, PDF, audio, or video file and return it as multimodal MCP content (base64-encoded, capped at 20 MiB; images auto-resized to model limits, large PDFs read in page ranges).",
"inputSchema": {
"additionalProperties": false,
"properties": {
"file_path": {
"description": "Path to an image (gif/jpeg/png/webp), PDF, audio (wav/mp3/aiff/aac/ogg/flac), or video (mp4/mpeg/mov/avi/flv/webm/wmv/3gpp) file. Use /workdir/ prefix for sandbox files, or an absolute path within the sandbox. Returns multimodal MCP content; images are automatically downscaled/re-encoded to fit model limits (max 2000px on the long edge). Audio/video require a model with native audio/video support (e.g. Gemini) \u2014 other models see only a text placeholder. Use readFile for text and readPDF for PDF text extraction.",
"type": "string"
},
"pages": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "PDF page range to read, 1-indexed, e.g. '3' or '1-10' (max 20 pages per read). Required for PDFs over 20 pages or 10 MiB. Ignored for images."
}
},
"required": [
"file_path"
],
"type": "object"
}
},
{
"name": "readPDF",
"description": "Extract text from a PDF file inside the sandbox.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"file_path": {
"type": "string"
}
},
"required": [
"file_path"
],
"type": "object"
}
},
{
"name": "writeFile",
"description": "Write content to a file in an isolated sandbox.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"file_path": {
"type": "string"
},
"content": {
"type": "string"
}
},
"required": [
"file_path",
"content"
],
"type": "object"
}
}
],
"toolsets": {
"read": [
"echo",
"listFiles",
"readFile",
"readPDF"
],
"read_multimodal": [
"echo",
"listFiles",
"readFile",
"readMedia",
"readPDF"
],
"write": [
"executeBash",
"writeFile"
],
"debug": [
"echo",
"executeBash",
"executePython"
],
"ds_all": [
"executeBash",
"executePython",
"listFiles",
"readFile",
"readPDF",
"writeFile"
],
"state": [
"export_state",
"import_state"
],
"grading": [
"prepareGradingContext"
]
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"run": {
"command": "python",
"args": ["-m", "syntara.server"],
"env": {
"SETUID": "1000",
"SETGID": "1000",
"HOME": "/home/model",
"USER": "model",
"LOGNAME": "model"
}
},
"setup": {
"command": "python",
"args": ["-m", "syntara.setup"],
"env": {
"SETUID": "1000",
"SETGID": "1000",
"HOME": "/home/model",
"USER": "model",
"LOGNAME": "model"
}
},
"namespaced": true,
"toolsets": {
"read": ["echo", "listFiles", "readFile", "readPDF"],
"read_multimodal": [
"echo",
"listFiles",
"readFile",
"readMedia",
"readPDF"
],
"write": ["executeBash", "writeFile"],
"debug": ["echo", "executeBash", "executePython"],
"ds_all": [
"executeBash",
"executePython",
"listFiles",
"readFile",
"readPDF",
"writeFile"
],
"state": ["export_state", "import_state"],
"grading": ["prepareGradingContext"]
}
}
+36
View File
@@ -0,0 +1,36 @@
[build-system]
build-backend = "uv_build"
requires = [ "uv-build>=0.11,<0.12" ]
[project]
# TEMPORARY backward-compatibility shim. ``syntara`` was renamed to ``core``;
# this package re-exposes the old ``syntara`` MCP surface (namespaced
# ``syntara__*`` tools, legacy toolset names, ``executePython``) by forwarding
# every call to ``core``. REMOVE this whole package after 2026-06-18 — see
# packages/syntara/syntara/_compat.py for the removal checklist.
name = "syntara"
version = "0.0.1"
description = "Temporary compatibility shim that forwards to the core MCP server"
requires-python = ">=3.13"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [
"core",
"fastmcp>=3.2,<4",
"mcp[cli]>=1.19",
"pydantic>=2",
]
scripts.syntara-mcp = "syntara.server:main"
[tool.uv]
environments = [
"sys_platform == 'darwin' and platform_machine == 'arm64'",
"sys_platform == 'linux' and platform_machine == 'x86_64'",
]
build-backend.module-root = ""
[tool.pytest]
ini_options.pythonpath = [ "." ]
@@ -0,0 +1,66 @@
"""Shared bits for the temporary ``syntara``→``core`` compatibility shim.
``syntara`` was renamed to ``core`` (tools are now exposed un-namespaced as
``bash``/``readFile``/… and ``executePython`` was dropped). This whole package
exists only to keep pre-rename projects working while they migrate: it re-
exposes the old ``syntara`` server (namespaced ``syntara__*`` tools, legacy
toolset names, ``executePython``) by forwarding every call to ``core``.
Every forwarded call logs a deprecation warning (grep service logs for
``DEPRECATED syntara compat``) so we can see which old features are still in use
and chase down the last callers.
REMOVE AFTER 2026-06-18. Checklist:
1. Delete the ``packages/syntara`` directory.
2. Drop ``packages/syntara`` from the root ``pyproject.toml`` workspace.members.
3. Remove the ``"syntara"`` entries from ``mcp_proxy/viewer.py`` name/icon maps.
4. Remove the both-syntara-and-core guard in ``mcp_proxy.commands.mcp``
(grep ``COMPAT_SHIM_NAME``).
"""
from __future__ import annotations
import functools
import inspect
import logging
import os
from collections.abc import Callable
from typing import Any
logger = logging.getLogger("syntara.compat")
REMOVAL_DATE = "2026-06-18"
def _log_forward(tool: str) -> None:
logger.warning(
"DEPRECATED syntara compat: tool %r called (forwarding to core). seed=%s. The 'syntara' "
"compatibility package is removed after %s — migrate to the 'core' tool/toolset names.",
tool,
os.environ.get("WORLDBENCH_SEED", "?"),
REMOVAL_DATE,
)
def forwarding(name: str, core_fn: Callable[..., Any]) -> Callable[..., Any]:
"""Wrap *core_fn* so each call logs a deprecation warning, then delegates.
``functools.wraps`` preserves the wrapped function's signature and docstring
so the MCP tool schema introspected from this wrapper is identical to
core's. The exposed tool name is still set explicitly at registration time.
The wrapper is a coroutine so the registered tool is async — core's tools are
now coroutines, and FastMCP must run validation on the event loop rather than
a worker threadpool (see ``async_tool_guard``).
"""
@functools.wraps(core_fn)
async def wrapper(*args, **kwargs):
_log_forward(name)
result = core_fn(*args, **kwargs)
if inspect.isawaitable(result):
result = await result
return result
wrapper.__name__ = name
return wrapper
@@ -0,0 +1,59 @@
"""Boot-time guard: every registered MCP tool must be async.
Sync (`def`) tools make FastMCP run pydantic argument validation in an anyio
worker threadpool. Under concurrent calls the shared pydantic-core validator is
re-entered across threads and panics with ``pyo3_runtime.PanicException:
dictionary changed size during iteration``, which tears down the
StreamableHTTP task group and turns every later request into a 500. Async
(`async def`) tools validate on the single-threaded event loop and are safe.
This guard runs at server boot (and therefore during ``mcp-proxy gen``, which
boots every server): a non-conformant package fails fast instead of shipping a
server that can crash under load. The same check is intentionally duplicated in
each package because the bundled prod images install only that package's own
dependencies, so there is no shared runtime module to import.
"""
from __future__ import annotations
import asyncio
import inspect
from functools import partial
from typing import Any
def _unwrap(fn: Any) -> Any:
while isinstance(fn, partial):
fn = fn.func
return fn
def find_sync_tools(mcp: Any) -> list[str]:
"""Return the names of registered tools whose function is not a coroutine."""
# mcp.server.fastmcp.FastMCP exposes a synchronous tool manager.
manager = getattr(mcp, "_tool_manager", None)
if manager is not None and hasattr(manager, "list_tools"):
return sorted(t.name for t in manager.list_tools() if not inspect.iscoroutinefunction(_unwrap(t.fn)))
# fastmcp.FastMCP exposes an async API.
async def _collect() -> list[str]:
sync: list[str] = []
for descriptor in await mcp.list_tools():
tool = await mcp.get_tool(descriptor.name)
if not inspect.iscoroutinefunction(_unwrap(tool.fn)):
sync.append(descriptor.name)
return sorted(sync)
return asyncio.run(_collect())
def assert_tools_async(mcp: Any) -> None:
"""Raise if any registered tool is synchronous."""
sync = find_sync_tools(mcp)
if sync:
raise RuntimeError(
"MCP tools must be async (`async def`). Sync tools run pydantic argument "
"validation in a worker threadpool and can trigger a pydantic-core "
"'dictionary changed size during iteration' panic under concurrent calls, "
"which kills the server. Make these tools async: " + ", ".join(sync)
)
+61
View File
@@ -0,0 +1,61 @@
"""Legacy ``syntara`` MCP server — a compatibility shim over ``core``.
Builds a FastMCP app named ``syntara`` whose tools forward to ``core`` (see
``syntara.tools``). Reuses core's privilege-drop, workdir, proxy-token, and
viewer machinery verbatim so the runtime behaviour is identical to core; only
the server name and the tool names/surface differ.
REMOVE with the rest of the ``syntara`` package after 2026-06-18 (see
``syntara._compat``).
"""
import os
from fastmcp import FastMCP
from fastmcp.tools.function_tool import FunctionTool
import syntara.tools as tools
from core._token import capture_proxy_token
from core.privilege import ensure_workdir
from core.tools import sandbox
from core.viewer import run_http_server
from syntara.async_tool_guard import assert_tools_async
def build_app() -> FastMCP:
"""Build a FastMCP app exposing the legacy syntara tools (forwarding to core)."""
app = FastMCP("syntara")
for tool_name in sorted(tools.__all__):
tool_fn = getattr(tools, tool_name, None)
if callable(tool_fn):
# output_schema=None matches core's server and the shipped
# mcp-tools.generated.json, which never advertised output schemas.
app.add_tool(FunctionTool.from_function(fn=tool_fn, name=tool_name, output_schema=None))
return app
@capture_proxy_token
def main() -> None:
# The server intentionally keeps running as root: it needs to read the
# locked-down /app tree, and a root server lets us close /opt/venv to uid
# 1000. Privilege is dropped per agent command instead — see
# syntara.tools.sandbox._privilege_drop_kwargs (the chokepoint every
# executeBash/executePython/file-tool subprocess flows through).
ensure_workdir()
# Land the running server in the agent's workdir so any tool that doesn't
# pass cwd= explicitly resolves relative paths under /workdir. Skip when
# the dir doesn't exist (CI / local dev where /workdir isn't provisioned).
if os.path.isdir(sandbox.WORKDIR):
os.chdir(sandbox.WORKDIR)
app = build_app()
assert_tools_async(app)
port = os.environ.get("PORT")
if port:
run_http_server(app, int(port))
else:
app.run(show_banner=False)
if __name__ == "__main__":
main()
+21
View File
@@ -0,0 +1,21 @@
"""Legacy ``syntara`` setup hook — forwards to ``core.setup``.
The setup step (copying uploaded context files into the workdir) is identical
for the shim, so we delegate to core's implementation and just log that the
legacy entrypoint was used.
REMOVE with the rest of the ``syntara`` package after 2026-06-18 (see
``syntara._compat``).
"""
from core.setup import main as _core_setup_main
from syntara._compat import _log_forward
def main() -> None:
_log_forward("setup")
_core_setup_main()
if __name__ == "__main__":
main()
@@ -0,0 +1,81 @@
"""Legacy ``syntara`` tool surface, forwarding to ``core``.
Every tool here delegates to the corresponding ``core`` tool (logging a
deprecation warning on each call). Two names differ from core:
* ``executeBash`` → core's renamed ``bash``.
* ``executePython`` → removed from core; reimplemented here as
``bash("python3 -c <code>")`` so pre-rename callers keep working.
REMOVE with the rest of the ``syntara`` package after 2026-06-18 (see
``syntara._compat``).
"""
from typing import Annotated, Any
from pydantic import Field
from core.tools import echo as _echo
from core.tools import export_state as _export_state
from core.tools import import_state as _import_state
from core.tools import listFiles as _listFiles
from core.tools import prepareGradingContext as _prepareGradingContext
from core.tools import readFile as _readFile
from core.tools import readMedia as _readMedia
from core.tools import readPDF as _readPDF
from core.tools import writeFile as _writeFile
from core.tools.bash import bash as _bash
from core.tools.sandbox import DEFAULT_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS, run_in_sandbox
from syntara._compat import _log_forward, forwarding
echo = forwarding("echo", _echo)
listFiles = forwarding("listFiles", _listFiles)
readFile = forwarding("readFile", _readFile)
readMedia = forwarding("readMedia", _readMedia)
readPDF = forwarding("readPDF", _readPDF)
writeFile = forwarding("writeFile", _writeFile)
prepareGradingContext = forwarding("prepareGradingContext", _prepareGradingContext)
export_state = forwarding("export_state", _export_state)
import_state = forwarding("import_state", _import_state)
# executeBash is just core's bash under its old name.
executeBash = forwarding("executeBash", _bash)
async def executePython(
code: Annotated[str, Field(description="The Python code to execute")],
timeout_seconds: Annotated[
int | None,
Field(
description=f"Timeout in seconds (default {DEFAULT_TIMEOUT_SECONDS}, max {MAX_TIMEOUT_SECONDS})",
ge=1,
le=MAX_TIMEOUT_SECONDS,
),
] = None,
) -> dict[str, Any]:
"""Execute Python code in an isolated directory.
``executePython`` was dropped from core; this shim reimplements it on core's
sandbox, piping the source to ``python3 -`` via stdin — matching the original
so large scripts aren't capped by the OS command-line length (``python3 -c
<code>`` would put the whole program in argv and can hit "Argument list too
long").
"""
_log_forward("executePython")
timeout = timeout_seconds or DEFAULT_TIMEOUT_SECONDS
return run_in_sandbox(["python3", "-"], timeout, input=code)
__all__ = [
"echo",
"executeBash",
"executePython",
"export_state",
"import_state",
"listFiles",
"prepareGradingContext",
"readFile",
"readMedia",
"readPDF",
"writeFile",
]
@@ -0,0 +1,90 @@
"""The syntara shim forwards every tool to core (with deprecation logging).
REMOVE with the rest of the syntara package after 2026-06-18.
"""
from __future__ import annotations
import logging
import pytest
import syntara.tools as tools
from core.tools import sandbox
@pytest.fixture
def workdir(tmp_path, monkeypatch):
monkeypatch.setattr(sandbox, "WORKDIR", str(tmp_path))
return tmp_path
def test_exposes_full_legacy_tool_surface():
assert set(tools.__all__) == {
"echo",
"executeBash",
"executePython",
"export_state",
"import_state",
"listFiles",
"prepareGradingContext",
"readFile",
"readMedia",
"readPDF",
"writeFile",
}
@pytest.mark.asyncio
async def test_echo_forwards_to_core():
# core.echo returns its message; the shim must return the same thing.
from core.tools import echo as core_echo
assert await tools.echo("ping") == await core_echo("ping")
@pytest.mark.asyncio
async def test_executebash_runs_a_command(workdir):
result = await tools.executeBash("echo hello-from-shim")
assert result["returncode"] == 0
assert "hello-from-shim" in result["stdout"]
@pytest.mark.asyncio
async def test_executepython_runs_python(workdir):
result = await tools.executePython("print(6 * 7)")
assert result["returncode"] == 0
assert result["stdout"].strip() == "42"
@pytest.mark.asyncio
async def test_executepython_handles_quotes(workdir):
# Source goes over stdin, so embedded quotes need no escaping.
result = await tools.executePython("print('a\"b\\'c')")
assert result["returncode"] == 0
assert result["stdout"].strip() == "a\"b'c"
@pytest.mark.asyncio
async def test_executepython_handles_large_script_over_stdin(workdir):
# The whole point of stdin delivery: a script far larger than the OS argv
# limit (ARG_MAX ~256 KiB on macOS) must run. `python3 -c <code>` would fail
# with "Argument list too long" before Python starts.
big = "A" * 300_000
result = await tools.executePython(f"print(len('{big}'))")
assert result["returncode"] == 0, result
assert result["stdout"].strip() == "300000"
@pytest.mark.asyncio
async def test_forwarded_call_logs_deprecation(workdir, caplog):
with caplog.at_level(logging.WARNING, logger="syntara.compat"):
await tools.executeBash("true")
assert any("DEPRECATED syntara compat" in r.message and "executeBash" in r.message for r in caplog.records)
@pytest.mark.asyncio
async def test_executepython_logs_under_its_own_name(workdir, caplog):
with caplog.at_level(logging.WARNING, logger="syntara.compat"):
await tools.executePython("pass")
assert any("executePython" in r.message for r in caplog.records)