toolplane 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {toolplane-0.2.0 → toolplane-0.3.0}/AGENTS.md +9 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/Makefile +3 -0
- toolplane-0.3.0/PKG-INFO +248 -0
- toolplane-0.3.0/README.md +221 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/ROADMAP.md +7 -5
- toolplane-0.3.0/docs/agents-dont-find-resources.md +90 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/architecture.md +6 -2
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/code-mode-backends.md +7 -3
- toolplane-0.3.0/docs/codex-elicitation-title-bug.md +102 -0
- toolplane-0.3.0/docs/configuration.md +283 -0
- toolplane-0.3.0/docs/mcp-client-capability-matrix.md +72 -0
- toolplane-0.3.0/docs/mcp-client-capability-spike.md +96 -0
- toolplane-0.3.0/docs/monty-session-spike.md +169 -0
- toolplane-0.3.0/docs/result-store-design.md +277 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/toolplane-mcp.md +37 -31
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/README.md +21 -0
- toolplane-0.3.0/examples/as_tool_langgraph.py +83 -0
- toolplane-0.3.0/examples/as_tool_openai_agents.py +68 -0
- toolplane-0.3.0/examples/as_tool_pydantic_ai.py +78 -0
- toolplane-0.3.0/examples/mcp_client_probe.py +144 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/mkdocs.yml +7 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/pyproject.toml +8 -2
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/__init__.py +7 -1
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/adapters/ambient_cli.py +172 -7
- toolplane-0.3.0/src/toolplane/artifacts.py +391 -0
- toolplane-0.3.0/src/toolplane/audit.py +99 -0
- toolplane-0.3.0/src/toolplane/backends/_python.py +134 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/backends/local.py +121 -3
- toolplane-0.3.0/src/toolplane/backends/monty.py +583 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/backends/pyodide_deno.py +225 -11
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/bridges/base.py +10 -0
- toolplane-0.3.0/src/toolplane/bridges/in_process.py +193 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/bridges/rpc.py +8 -1
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/capabilities.py +19 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/config.py +55 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/discovery.py +10 -2
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/errors.py +18 -2
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/execution.py +4 -0
- toolplane-0.3.0/src/toolplane/mcp_facade.py +366 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/mcp_lifecycle.py +5 -4
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/registry.py +90 -5
- toolplane-0.3.0/src/toolplane/results.py +301 -0
- toolplane-0.3.0/src/toolplane/runtime.py +718 -0
- toolplane-0.3.0/src/toolplane/skills/driving-toolplane/SKILL.md +168 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_ambient_cli.py +128 -0
- toolplane-0.3.0/tests/test_artifact_store.py +163 -0
- toolplane-0.3.0/tests/test_as_tool.py +226 -0
- toolplane-0.3.0/tests/test_audit_log.py +340 -0
- toolplane-0.3.0/tests/test_cli_escalation.py +515 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_cli_foundation.py +1 -1
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_cli_to_py_adapter.py +73 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_mcp_facade.py +198 -7
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_mcp_lifecycle.py +4 -4
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_monty_backend.py +170 -0
- toolplane-0.3.0/tests/test_monty_session.py +392 -0
- toolplane-0.3.0/tests/test_pyodide_deno_backend.py +327 -0
- toolplane-0.3.0/tests/test_result_store.py +549 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_toolplane.py +106 -0
- toolplane-0.2.0/PKG-INFO +0 -284
- toolplane-0.2.0/README.md +0 -257
- toolplane-0.2.0/docs/configuration.md +0 -163
- toolplane-0.2.0/src/toolplane/backends/_python.py +0 -12
- toolplane-0.2.0/src/toolplane/backends/monty.py +0 -245
- toolplane-0.2.0/src/toolplane/bridges/in_process.py +0 -68
- toolplane-0.2.0/src/toolplane/mcp_facade.py +0 -126
- toolplane-0.2.0/src/toolplane/runtime.py +0 -259
- toolplane-0.2.0/tests/test_pyodide_deno_backend.py +0 -132
- {toolplane-0.2.0 → toolplane-0.3.0}/.github/workflows/ci.yml +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/.github/workflows/pages.yml +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/.gitignore +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/CLAUDE.md +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/development/release-checklist.md +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/examples/ambient-cli.md +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/examples/index.md +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/examples/mcp-tools.md +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/index.md +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/mcp-lifecycle-spike.md +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/monty-default-spike.md +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/docs/stylesheets/extra.css +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/ambient_cli_git.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/context7_remote.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/fastmcp_in_process.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/from_config.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/mcp_stdio_config.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/mcp_stdio_server.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/mcp_text_server.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/mixed_capability_report.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/multi_server.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/multi_server.toml +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/examples/scoped_namespaces_context7.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/adapters/__init__.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/adapters/cli_to_py.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/adapters/mcp.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/adapters/python.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/backends/__init__.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/backends/base.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/bridges/__init__.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/cli.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/config_edit.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/doctor.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/src/toolplane/policy.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_bridges.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_config.py +0 -0
- {toolplane-0.2.0 → toolplane-0.3.0}/tests/test_mcp_adapter.py +0 -0
|
@@ -54,6 +54,15 @@ more custom code around it.
|
|
|
54
54
|
capability envelope.
|
|
55
55
|
- Pyodide+Deno is the opt-in sandbox for package-capable snippets, especially
|
|
56
56
|
pandas/NumPy-style workflows.
|
|
57
|
+
- Monty sessions (`docs/monty-session-spike.md`) persist the live namespace
|
|
58
|
+
across runs by default (stdio/embedded only): a timed-out run rolls the
|
|
59
|
+
namespace back via a pre-run snapshot — never remove that snapshot, it is
|
|
60
|
+
what keeps a cancelled run from poisoning the interpreter
|
|
61
|
+
(pydantic/monty#533) — and host-side effects are never rolled back.
|
|
62
|
+
- The result store (`save_result`/`load_result`, `docs/result-store-design.md`)
|
|
63
|
+
persists JSON-shaped data across runs within one process; it never persists
|
|
64
|
+
interpreters or anything to disk. With sessions on, its remaining jobs are
|
|
65
|
+
surviving a session reset, crossing backends, and MCP-resource reads.
|
|
57
66
|
- Docker, Modal, E2B, and Blaxel are needed for arbitrary CPython packages,
|
|
58
67
|
native system dependencies, subprocesses, GPUs, remote isolation, or
|
|
59
68
|
long-running jobs.
|
|
@@ -27,6 +27,9 @@ examples:
|
|
|
27
27
|
$(UV) run --no-project --with-editable . python examples/mcp_stdio_config.py
|
|
28
28
|
$(UV) run --no-project --with-editable . python examples/multi_server.py
|
|
29
29
|
$(UV) run --no-project --with-editable . python examples/from_config.py
|
|
30
|
+
env -u ANTHROPIC_API_KEY -u OPENAI_API_KEY $(UV) run --no-project --with-editable . --with pydantic-ai python examples/as_tool_pydantic_ai.py
|
|
31
|
+
env -u ANTHROPIC_API_KEY -u OPENAI_API_KEY $(UV) run --no-project --with-editable . --with openai-agents python examples/as_tool_openai_agents.py
|
|
32
|
+
env -u ANTHROPIC_API_KEY -u OPENAI_API_KEY $(UV) run --no-project --with-editable . --with langchain-core python examples/as_tool_langgraph.py
|
|
30
33
|
|
|
31
34
|
docs:
|
|
32
35
|
$(UV) run --no-project --with-editable ".[docs]" mkdocs build --strict --site-dir $(SITE_DIR)
|
toolplane-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: toolplane
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A controlled Python code-mode runtime for programmable tool surfaces.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: agents,cli,code-mode,mcp,runtime,tools
|
|
7
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: cli-to-py>=0.2
|
|
17
|
+
Requires-Dist: fastmcp>=3.1
|
|
18
|
+
Requires-Dist: pydantic-monty<0.0.19,>=0.0.18
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
20
|
+
Requires-Dist: tomlkit>=0.15.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
23
|
+
Provides-Extra: docs
|
|
24
|
+
Requires-Dist: mkdocs-material<9.7,>=9.5; extra == 'docs'
|
|
25
|
+
Requires-Dist: mkdocs>=1.6; extra == 'docs'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# toolplane
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/toolplane/)
|
|
31
|
+
[](https://pypi.org/project/toolplane/)
|
|
32
|
+
[](https://github.com/oneryalcin/toolplane/actions/workflows/ci.yml)
|
|
33
|
+
[](https://github.com/oneryalcin/toolplane/actions/workflows/pages.yml)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
|
|
36
|
+
Define your agent's tool surface **once** — MCP servers, CLI binaries, Python
|
|
37
|
+
functions — and get the same curated, sandboxed Python namespace under every
|
|
38
|
+
agent client you use: Claude Code, Codex, opencode, or your own.
|
|
39
|
+
|
|
40
|
+
Full documentation: https://oneryalcin.github.io/toolplane/
|
|
41
|
+
|
|
42
|
+
## Why
|
|
43
|
+
|
|
44
|
+
Agents are strongest when they use code as the control plane: looping,
|
|
45
|
+
filtering, retrying, and combining tools in one pass instead of bouncing
|
|
46
|
+
through one tool call at a time. Toolplane is the runtime for that:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
discover capabilities -> inspect schemas -> execute Python against a curated namespace
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Instead of registering N MCP servers in every agent client (and paying for
|
|
53
|
+
N × dozens of tool schemas in every context window, in every client, with
|
|
54
|
+
every client's different quirks), you register **one** server that exposes
|
|
55
|
+
three meta-tools. Your agent searches for what it needs, reads the exact
|
|
56
|
+
schemas, and writes a snippet:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# one execute_code call replaces a dozen round-trips
|
|
60
|
+
rows = []
|
|
61
|
+
for repo in ["toolplane", "cli-to-py"]:
|
|
62
|
+
prs = await github_list_pull_requests(repo=repo, state="merged", limit=25)
|
|
63
|
+
for pr in prs:
|
|
64
|
+
rows.append({"repo": repo, "title": pr["title"], "days_open": pr["days_open"]})
|
|
65
|
+
handle = await save_result(rows, label="pr-audit")
|
|
66
|
+
return {"count": len(rows), "handle": handle}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
What makes toolplane different from other code-mode runtimes:
|
|
70
|
+
|
|
71
|
+
- **CLI binaries are first-class, policy-gated capabilities.** `git`, `gh`,
|
|
72
|
+
`jq` become async Python functions behind an allowlist you control — not a
|
|
73
|
+
raw shell. No other code-mode runtime ships this lane.
|
|
74
|
+
- **Policy escalates to a human instead of dead-ending.** If a snippet needs
|
|
75
|
+
a binary outside the allowlist, your agent client shows *you* a form:
|
|
76
|
+
allow it for this session, or refuse. Declines stick; nothing is ever
|
|
77
|
+
written back to config. (MCP elicitation; degrades to a plain refusal on
|
|
78
|
+
clients that can't prompt.)
|
|
79
|
+
- **Safe by default, with zero infrastructure.** The default backend
|
|
80
|
+
([Monty](https://github.com/pydantic/monty)) is a sandboxed interpreter
|
|
81
|
+
with no filesystem or network access — a pure `pip install`, no Docker, no
|
|
82
|
+
Deno, no daemon.
|
|
83
|
+
- **Engineered for how agents actually behave.** Every dead end emits a
|
|
84
|
+
signpost (no-match searches say how to browse; policy errors name what is
|
|
85
|
+
allowed), errors are catchable by builtin type on every backend, and the
|
|
86
|
+
live namespace manifest (`toolplane://namespace`) never lies about the
|
|
87
|
+
server's configuration. This surface is certified by cold-start agent
|
|
88
|
+
sessions, not just unit tests — the design lessons are published in
|
|
89
|
+
[Agents Don't Find Resources](https://oneryalcin.github.io/toolplane/agents-dont-find-resources/)
|
|
90
|
+
and the firsthand
|
|
91
|
+
[MCP Client Capability Matrix](https://oneryalcin.github.io/toolplane/mcp-client-capability-matrix/).
|
|
92
|
+
|
|
93
|
+
## Quickstart
|
|
94
|
+
|
|
95
|
+
Set up a config (safe defaults: sandboxed backend, CLI disabled):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uvx toolplane init
|
|
99
|
+
uvx toolplane cli allow git
|
|
100
|
+
uvx toolplane mcp add context7 --url https://mcp.context7.com/mcp
|
|
101
|
+
uvx toolplane doctor
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Prove it runs — put this in `first.py`:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
v = await git("version")
|
|
108
|
+
return {"ok": v["ok"], "git": v["stdout"].strip()}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uvx toolplane run first.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Then register it with your agent client (use the absolute path to your
|
|
116
|
+
`toolplane.toml`):
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Claude Code
|
|
120
|
+
claude mcp add toolplane -- uvx toolplane serve mcp --config /path/to/toolplane.toml
|
|
121
|
+
|
|
122
|
+
# Codex
|
|
123
|
+
codex mcp add toolplane -- uvx toolplane serve mcp --config /path/to/toolplane.toml
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```jsonc
|
|
127
|
+
// opencode (~/.config/opencode/opencode.json)
|
|
128
|
+
{
|
|
129
|
+
"mcp": {
|
|
130
|
+
"toolplane": {
|
|
131
|
+
"type": "local",
|
|
132
|
+
"command": ["uvx", "toolplane", "serve", "mcp", "--config", "/path/to/toolplane.toml"]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Every client now sees the same three tools — `search_capabilities`,
|
|
139
|
+
`get_capability_schemas`, `execute_code` — plus the live manifest resource
|
|
140
|
+
`toolplane://namespace` and a bundled usage skill. Ask your agent to read the
|
|
141
|
+
manifest and go.
|
|
142
|
+
|
|
143
|
+
## What your agent gets
|
|
144
|
+
|
|
145
|
+
- **Three meta-tools instead of a tool catalog.** Search is exact-word (an
|
|
146
|
+
empty query lists everything); schemas come back only for the capabilities
|
|
147
|
+
the snippet will actually use.
|
|
148
|
+
- **A flat, async Python namespace.** MCP tools as flat functions —
|
|
149
|
+
`await context7_get_docs(...)` — or canonically as
|
|
150
|
+
`await call_tool("mcp:context7/get_docs", {...})`; allowlisted CLI
|
|
151
|
+
binaries as `await git("log", oneline=True, max_count=3)` returning
|
|
152
|
+
`{'stdout', 'stderr', 'exit_code', 'ok'}`. (Scoped sugar like
|
|
153
|
+
`context7.get_docs(...)` exists on the non-default backends; the monty
|
|
154
|
+
default is flat-only — the manifest always shows the shapes that work.)
|
|
155
|
+
- **State between runs, off the context window.** Variables persist across
|
|
156
|
+
runs like notebook cells (a timed-out run rolls back cleanly;
|
|
157
|
+
`await reset_session()` starts fresh). For values that must survive a
|
|
158
|
+
reset or be read as MCP resources: `save_result`/`load_result` for
|
|
159
|
+
JSON-shaped data, `save_artifact`/`load_artifact` for bytes (CSVs,
|
|
160
|
+
images, parquet) — on Claude Code, a saved artifact materializes as a
|
|
161
|
+
real local file.
|
|
162
|
+
- **Errors written for agents, not log files.** The real exception type and
|
|
163
|
+
message on every backend, catchable by builtin type (`except ValueError`
|
|
164
|
+
for store failures, `except PermissionError` for CLI policy,
|
|
165
|
+
`except LookupError` for unknown capabilities) — identically on all three
|
|
166
|
+
backends.
|
|
167
|
+
|
|
168
|
+
## Use it as a library
|
|
169
|
+
|
|
170
|
+
The same runtime embeds directly in Python — register your own functions and
|
|
171
|
+
execute agent-written snippets against them, sandboxed, with no server in
|
|
172
|
+
between:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from toolplane import Toolplane
|
|
176
|
+
|
|
177
|
+
runtime = Toolplane()
|
|
178
|
+
|
|
179
|
+
@runtime.tool(tags={"math"})
|
|
180
|
+
def add(x: int, y: int) -> int:
|
|
181
|
+
"""Add two numbers."""
|
|
182
|
+
return x + y
|
|
183
|
+
|
|
184
|
+
result = await runtime.execute("""
|
|
185
|
+
value = await call_tool("add", {"x": 2, "y": 3})
|
|
186
|
+
return value
|
|
187
|
+
""")
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
And if you already have an agent framework, `as_tool()` packages the whole
|
|
191
|
+
runtime as one `run_code` tool — a plain async function with a generated
|
|
192
|
+
description (compact enough for OpenAI's tool-description cap) that
|
|
193
|
+
pydantic-ai, the OpenAI Agents SDK, and LangChain/LangGraph all accept
|
|
194
|
+
directly. It is sandboxed monty by default: model-authored code never
|
|
195
|
+
inherits the `local_unsafe` dev backend implicitly — that requires an
|
|
196
|
+
explicit `backend="local_unsafe"` opt-in:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from pydantic_ai import Agent
|
|
200
|
+
|
|
201
|
+
agent = Agent("anthropic:claude-sonnet-5", tools=[runtime.as_tool()])
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
MCP servers, explicit CLI wrappers, and scoped Python namespaces register the
|
|
205
|
+
same way — see the [library guide](https://oneryalcin.github.io/toolplane/)
|
|
206
|
+
and [examples](examples/README.md) for the full surface, including
|
|
207
|
+
config-driven setup (`Toolplane.from_config`), the result/artifact stores,
|
|
208
|
+
and runnable `as_tool` examples for all three frameworks.
|
|
209
|
+
|
|
210
|
+
## Backends
|
|
211
|
+
|
|
212
|
+
| Backend | Use | Status |
|
|
213
|
+
| --- | --- | --- |
|
|
214
|
+
| `monty` | Default. Sandboxed by construction (no filesystem/network), pure pip install. Flat callable namespace — no classes, no third-party imports. | Active |
|
|
215
|
+
| `pyodide-deno` | Opt-in for package-capable snippets (pandas/NumPy-style) via Pyodide in Deno. | Supported, feature-frozen |
|
|
216
|
+
| `local_unsafe` | In-process execution for development only. | Dev only |
|
|
217
|
+
|
|
218
|
+
A container backend (real CPython, arbitrary packages) lands when a concrete
|
|
219
|
+
use case needs it. See [Code Mode Backends](docs/code-mode-backends.md) for
|
|
220
|
+
the design record.
|
|
221
|
+
|
|
222
|
+
## Design goals
|
|
223
|
+
|
|
224
|
+
- Normalize heterogeneous tools into a Python-first API surface; JSON is the
|
|
225
|
+
wire format, not the programming model.
|
|
226
|
+
- Keep the exposed runtime curated rather than ambiently powerful; the host
|
|
227
|
+
decides which capabilities exist and what boundaries apply.
|
|
228
|
+
- Durable policy lives in config; session policy (escalation grants) dies
|
|
229
|
+
with the process.
|
|
230
|
+
- Canonical capability ids are qualified (`mcp:server/tool`); friendly
|
|
231
|
+
aliases exist only when unambiguous.
|
|
232
|
+
- Every slice earns its place through behavior, tests, or live certification.
|
|
233
|
+
|
|
234
|
+
## Development
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
make test
|
|
238
|
+
make examples
|
|
239
|
+
make ci
|
|
240
|
+
make publish-check
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Docs: `make docs` / `make docs-serve`. Publishing:
|
|
244
|
+
`PYPI_TOKEN=... make publish` — see the
|
|
245
|
+
[release checklist](docs/development/release-checklist.md).
|
|
246
|
+
|
|
247
|
+
See [ROADMAP.md](ROADMAP.md) for sequencing and
|
|
248
|
+
[Architecture](docs/architecture.md) for code organization.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# toolplane
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/toolplane/)
|
|
4
|
+
[](https://pypi.org/project/toolplane/)
|
|
5
|
+
[](https://github.com/oneryalcin/toolplane/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/oneryalcin/toolplane/actions/workflows/pages.yml)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
Define your agent's tool surface **once** — MCP servers, CLI binaries, Python
|
|
10
|
+
functions — and get the same curated, sandboxed Python namespace under every
|
|
11
|
+
agent client you use: Claude Code, Codex, opencode, or your own.
|
|
12
|
+
|
|
13
|
+
Full documentation: https://oneryalcin.github.io/toolplane/
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
Agents are strongest when they use code as the control plane: looping,
|
|
18
|
+
filtering, retrying, and combining tools in one pass instead of bouncing
|
|
19
|
+
through one tool call at a time. Toolplane is the runtime for that:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
discover capabilities -> inspect schemas -> execute Python against a curated namespace
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Instead of registering N MCP servers in every agent client (and paying for
|
|
26
|
+
N × dozens of tool schemas in every context window, in every client, with
|
|
27
|
+
every client's different quirks), you register **one** server that exposes
|
|
28
|
+
three meta-tools. Your agent searches for what it needs, reads the exact
|
|
29
|
+
schemas, and writes a snippet:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
# one execute_code call replaces a dozen round-trips
|
|
33
|
+
rows = []
|
|
34
|
+
for repo in ["toolplane", "cli-to-py"]:
|
|
35
|
+
prs = await github_list_pull_requests(repo=repo, state="merged", limit=25)
|
|
36
|
+
for pr in prs:
|
|
37
|
+
rows.append({"repo": repo, "title": pr["title"], "days_open": pr["days_open"]})
|
|
38
|
+
handle = await save_result(rows, label="pr-audit")
|
|
39
|
+
return {"count": len(rows), "handle": handle}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
What makes toolplane different from other code-mode runtimes:
|
|
43
|
+
|
|
44
|
+
- **CLI binaries are first-class, policy-gated capabilities.** `git`, `gh`,
|
|
45
|
+
`jq` become async Python functions behind an allowlist you control — not a
|
|
46
|
+
raw shell. No other code-mode runtime ships this lane.
|
|
47
|
+
- **Policy escalates to a human instead of dead-ending.** If a snippet needs
|
|
48
|
+
a binary outside the allowlist, your agent client shows *you* a form:
|
|
49
|
+
allow it for this session, or refuse. Declines stick; nothing is ever
|
|
50
|
+
written back to config. (MCP elicitation; degrades to a plain refusal on
|
|
51
|
+
clients that can't prompt.)
|
|
52
|
+
- **Safe by default, with zero infrastructure.** The default backend
|
|
53
|
+
([Monty](https://github.com/pydantic/monty)) is a sandboxed interpreter
|
|
54
|
+
with no filesystem or network access — a pure `pip install`, no Docker, no
|
|
55
|
+
Deno, no daemon.
|
|
56
|
+
- **Engineered for how agents actually behave.** Every dead end emits a
|
|
57
|
+
signpost (no-match searches say how to browse; policy errors name what is
|
|
58
|
+
allowed), errors are catchable by builtin type on every backend, and the
|
|
59
|
+
live namespace manifest (`toolplane://namespace`) never lies about the
|
|
60
|
+
server's configuration. This surface is certified by cold-start agent
|
|
61
|
+
sessions, not just unit tests — the design lessons are published in
|
|
62
|
+
[Agents Don't Find Resources](https://oneryalcin.github.io/toolplane/agents-dont-find-resources/)
|
|
63
|
+
and the firsthand
|
|
64
|
+
[MCP Client Capability Matrix](https://oneryalcin.github.io/toolplane/mcp-client-capability-matrix/).
|
|
65
|
+
|
|
66
|
+
## Quickstart
|
|
67
|
+
|
|
68
|
+
Set up a config (safe defaults: sandboxed backend, CLI disabled):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uvx toolplane init
|
|
72
|
+
uvx toolplane cli allow git
|
|
73
|
+
uvx toolplane mcp add context7 --url https://mcp.context7.com/mcp
|
|
74
|
+
uvx toolplane doctor
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Prove it runs — put this in `first.py`:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
v = await git("version")
|
|
81
|
+
return {"ok": v["ok"], "git": v["stdout"].strip()}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uvx toolplane run first.py
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then register it with your agent client (use the absolute path to your
|
|
89
|
+
`toolplane.toml`):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Claude Code
|
|
93
|
+
claude mcp add toolplane -- uvx toolplane serve mcp --config /path/to/toolplane.toml
|
|
94
|
+
|
|
95
|
+
# Codex
|
|
96
|
+
codex mcp add toolplane -- uvx toolplane serve mcp --config /path/to/toolplane.toml
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```jsonc
|
|
100
|
+
// opencode (~/.config/opencode/opencode.json)
|
|
101
|
+
{
|
|
102
|
+
"mcp": {
|
|
103
|
+
"toolplane": {
|
|
104
|
+
"type": "local",
|
|
105
|
+
"command": ["uvx", "toolplane", "serve", "mcp", "--config", "/path/to/toolplane.toml"]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Every client now sees the same three tools — `search_capabilities`,
|
|
112
|
+
`get_capability_schemas`, `execute_code` — plus the live manifest resource
|
|
113
|
+
`toolplane://namespace` and a bundled usage skill. Ask your agent to read the
|
|
114
|
+
manifest and go.
|
|
115
|
+
|
|
116
|
+
## What your agent gets
|
|
117
|
+
|
|
118
|
+
- **Three meta-tools instead of a tool catalog.** Search is exact-word (an
|
|
119
|
+
empty query lists everything); schemas come back only for the capabilities
|
|
120
|
+
the snippet will actually use.
|
|
121
|
+
- **A flat, async Python namespace.** MCP tools as flat functions —
|
|
122
|
+
`await context7_get_docs(...)` — or canonically as
|
|
123
|
+
`await call_tool("mcp:context7/get_docs", {...})`; allowlisted CLI
|
|
124
|
+
binaries as `await git("log", oneline=True, max_count=3)` returning
|
|
125
|
+
`{'stdout', 'stderr', 'exit_code', 'ok'}`. (Scoped sugar like
|
|
126
|
+
`context7.get_docs(...)` exists on the non-default backends; the monty
|
|
127
|
+
default is flat-only — the manifest always shows the shapes that work.)
|
|
128
|
+
- **State between runs, off the context window.** Variables persist across
|
|
129
|
+
runs like notebook cells (a timed-out run rolls back cleanly;
|
|
130
|
+
`await reset_session()` starts fresh). For values that must survive a
|
|
131
|
+
reset or be read as MCP resources: `save_result`/`load_result` for
|
|
132
|
+
JSON-shaped data, `save_artifact`/`load_artifact` for bytes (CSVs,
|
|
133
|
+
images, parquet) — on Claude Code, a saved artifact materializes as a
|
|
134
|
+
real local file.
|
|
135
|
+
- **Errors written for agents, not log files.** The real exception type and
|
|
136
|
+
message on every backend, catchable by builtin type (`except ValueError`
|
|
137
|
+
for store failures, `except PermissionError` for CLI policy,
|
|
138
|
+
`except LookupError` for unknown capabilities) — identically on all three
|
|
139
|
+
backends.
|
|
140
|
+
|
|
141
|
+
## Use it as a library
|
|
142
|
+
|
|
143
|
+
The same runtime embeds directly in Python — register your own functions and
|
|
144
|
+
execute agent-written snippets against them, sandboxed, with no server in
|
|
145
|
+
between:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from toolplane import Toolplane
|
|
149
|
+
|
|
150
|
+
runtime = Toolplane()
|
|
151
|
+
|
|
152
|
+
@runtime.tool(tags={"math"})
|
|
153
|
+
def add(x: int, y: int) -> int:
|
|
154
|
+
"""Add two numbers."""
|
|
155
|
+
return x + y
|
|
156
|
+
|
|
157
|
+
result = await runtime.execute("""
|
|
158
|
+
value = await call_tool("add", {"x": 2, "y": 3})
|
|
159
|
+
return value
|
|
160
|
+
""")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
And if you already have an agent framework, `as_tool()` packages the whole
|
|
164
|
+
runtime as one `run_code` tool — a plain async function with a generated
|
|
165
|
+
description (compact enough for OpenAI's tool-description cap) that
|
|
166
|
+
pydantic-ai, the OpenAI Agents SDK, and LangChain/LangGraph all accept
|
|
167
|
+
directly. It is sandboxed monty by default: model-authored code never
|
|
168
|
+
inherits the `local_unsafe` dev backend implicitly — that requires an
|
|
169
|
+
explicit `backend="local_unsafe"` opt-in:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from pydantic_ai import Agent
|
|
173
|
+
|
|
174
|
+
agent = Agent("anthropic:claude-sonnet-5", tools=[runtime.as_tool()])
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
MCP servers, explicit CLI wrappers, and scoped Python namespaces register the
|
|
178
|
+
same way — see the [library guide](https://oneryalcin.github.io/toolplane/)
|
|
179
|
+
and [examples](examples/README.md) for the full surface, including
|
|
180
|
+
config-driven setup (`Toolplane.from_config`), the result/artifact stores,
|
|
181
|
+
and runnable `as_tool` examples for all three frameworks.
|
|
182
|
+
|
|
183
|
+
## Backends
|
|
184
|
+
|
|
185
|
+
| Backend | Use | Status |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `monty` | Default. Sandboxed by construction (no filesystem/network), pure pip install. Flat callable namespace — no classes, no third-party imports. | Active |
|
|
188
|
+
| `pyodide-deno` | Opt-in for package-capable snippets (pandas/NumPy-style) via Pyodide in Deno. | Supported, feature-frozen |
|
|
189
|
+
| `local_unsafe` | In-process execution for development only. | Dev only |
|
|
190
|
+
|
|
191
|
+
A container backend (real CPython, arbitrary packages) lands when a concrete
|
|
192
|
+
use case needs it. See [Code Mode Backends](docs/code-mode-backends.md) for
|
|
193
|
+
the design record.
|
|
194
|
+
|
|
195
|
+
## Design goals
|
|
196
|
+
|
|
197
|
+
- Normalize heterogeneous tools into a Python-first API surface; JSON is the
|
|
198
|
+
wire format, not the programming model.
|
|
199
|
+
- Keep the exposed runtime curated rather than ambiently powerful; the host
|
|
200
|
+
decides which capabilities exist and what boundaries apply.
|
|
201
|
+
- Durable policy lives in config; session policy (escalation grants) dies
|
|
202
|
+
with the process.
|
|
203
|
+
- Canonical capability ids are qualified (`mcp:server/tool`); friendly
|
|
204
|
+
aliases exist only when unambiguous.
|
|
205
|
+
- Every slice earns its place through behavior, tests, or live certification.
|
|
206
|
+
|
|
207
|
+
## Development
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
make test
|
|
211
|
+
make examples
|
|
212
|
+
make ci
|
|
213
|
+
make publish-check
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Docs: `make docs` / `make docs-serve`. Publishing:
|
|
217
|
+
`PYPI_TOKEN=... make publish` — see the
|
|
218
|
+
[release checklist](docs/development/release-checklist.md).
|
|
219
|
+
|
|
220
|
+
See [ROADMAP.md](ROADMAP.md) for sequencing and
|
|
221
|
+
[Architecture](docs/architecture.md) for code organization.
|
|
@@ -12,7 +12,7 @@ local_unsafe slice
|
|
|
12
12
|
-> MCP adapter
|
|
13
13
|
-> config-driven runtime setup
|
|
14
14
|
-> Toolplane MCP facade
|
|
15
|
-
->
|
|
15
|
+
-> container backend (when a named user needs arbitrary CPython packages)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
The goal is to test the core architecture before building a broad framework
|
|
@@ -22,10 +22,12 @@ around it.
|
|
|
22
22
|
|
|
23
23
|
### Execution Backends
|
|
24
24
|
|
|
25
|
-
- Pyodide+Deno backend for package-capable sandboxed snippets
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
-
|
|
25
|
+
- Pyodide+Deno backend for package-capable sandboxed snippets — shipped and
|
|
26
|
+
feature-frozen: supported as-is, no further edge investment; its use case
|
|
27
|
+
is served by a container backend when a real user needs it.
|
|
28
|
+
- One container backend (real CPython, arbitrary packages) when a named user
|
|
29
|
+
asks. Modal/E2B/Blaxel-style remote backends only after that contract is
|
|
30
|
+
proven — not before.
|
|
29
31
|
|
|
30
32
|
### Capability Adapters
|
|
31
33
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Agents Don't Find Resources
|
|
2
|
+
|
|
3
|
+
A design lesson from building toolplane's discovery surface, verified live
|
|
4
|
+
against real agents. It generalizes to any MCP server: **exposing a
|
|
5
|
+
capability is not the same as an agent discovering it.** If you ship a
|
|
6
|
+
resource, a template, or a clever affordance and don't put a plain-text
|
|
7
|
+
pointer to it in a channel the agent already reads, you shipped nothing.
|
|
8
|
+
|
|
9
|
+
## The experiment that settled it
|
|
10
|
+
|
|
11
|
+
While building toolplane's namespace manifest (a live MCP resource
|
|
12
|
+
describing every binding an `execute_code` snippet can use), we ran cold
|
|
13
|
+
sessions: a fresh Claude Code agent, the toolplane server connected, a task
|
|
14
|
+
that the manifest would make easy. The agent had the resource-listing tool
|
|
15
|
+
available from turn one.
|
|
16
|
+
|
|
17
|
+
It never called it. Asked directly afterward — *"if we hadn't told you the
|
|
18
|
+
server exposes resources, would you have discovered them?"* — the agent's
|
|
19
|
+
answer was no. It guessed capability names, misread error text, and
|
|
20
|
+
retried, but at no point did "enumerate the server's resources" enter its
|
|
21
|
+
loop. The capability was present; the behavior wasn't.
|
|
22
|
+
|
|
23
|
+
That single observation reshaped the whole design: **the resource is the
|
|
24
|
+
payload; the pointer is the feature.**
|
|
25
|
+
|
|
26
|
+
## The three channels agents actually read
|
|
27
|
+
|
|
28
|
+
Discovery works only through text that arrives in the agent's context in
|
|
29
|
+
the normal course of work:
|
|
30
|
+
|
|
31
|
+
1. **Tool descriptions and server instructions** — read once, at session
|
|
32
|
+
start. This is where standing directives live: toolplane's
|
|
33
|
+
`execute_code` description says *"Read the toolplane://namespace
|
|
34
|
+
resource BEFORE writing code — guessing shapes fails."* Cold agents
|
|
35
|
+
follow it.
|
|
36
|
+
2. **Tool response text on the happy path** — read at the moment of need.
|
|
37
|
+
When a toolplane snippet saves an artifact, the response carries the
|
|
38
|
+
handle *and* its resource URI; the agent never has to know that
|
|
39
|
+
artifacts are enumerable, because the one it needs is already named.
|
|
40
|
+
3. **Error text on the failure path** — the strongest channel, because a
|
|
41
|
+
failing agent is a searching agent. Every dead end must emit a
|
|
42
|
+
signpost: toolplane's no-match search reports how many capabilities
|
|
43
|
+
exist and how to list them; a policy refusal names the allowed
|
|
44
|
+
binaries; a wrong call shape shows the right one, verbatim, ready to
|
|
45
|
+
paste.
|
|
46
|
+
|
|
47
|
+
Listings, templates, and documentation pages are not channels. Claude Code
|
|
48
|
+
doesn't even show templated resource URIs in its listing (see the
|
|
49
|
+
[capability matrix](mcp-client-capability-matrix.md)) — but even where
|
|
50
|
+
listings work, agents don't browse; they act, fail, and read the error.
|
|
51
|
+
|
|
52
|
+
## The corollary we learned the hard way: signposts can dangle
|
|
53
|
+
|
|
54
|
+
The doctrine has a failure mode of its own, found when toolplane grew an
|
|
55
|
+
embedded mode (`runtime.as_tool()` — the same runtime as a single tool
|
|
56
|
+
inside pydantic-ai / OpenAI Agents SDK / LangGraph, no MCP anywhere).
|
|
57
|
+
|
|
58
|
+
A live agent run misremembered a capability name. The error helpfully
|
|
59
|
+
said: *search capabilities with an empty query, or read the
|
|
60
|
+
toolplane://namespace resource.* Neither exists in embedded mode — there
|
|
61
|
+
is no search tool and no resource protocol. The agent obediently tried to
|
|
62
|
+
"search" through the only tool it had, hit a second dead end, and gave up
|
|
63
|
+
on a task it was fully equipped to complete.
|
|
64
|
+
|
|
65
|
+
So the refined rule: **a signpost must only point at affordances that
|
|
66
|
+
exist in the reader's context — or carry the answer inline.** The fix was
|
|
67
|
+
to make the dead ends self-contained: the unknown-capability error now
|
|
68
|
+
lists the registered names itself; a miss on a CLI binary's name teaches
|
|
69
|
+
the correct call shape in the message; the tool description shows a real
|
|
70
|
+
canonical id instead of a `"canonical:name"` placeholder (which one model
|
|
71
|
+
read literally, inventing `canonical:git`). After the change, the same
|
|
72
|
+
agent completed the same task — the signposts walked it there.
|
|
73
|
+
|
|
74
|
+
## Design rules, compressed
|
|
75
|
+
|
|
76
|
+
- Every surface ships with its pointer, in a channel from the list above.
|
|
77
|
+
A resource without a pointer is dark matter.
|
|
78
|
+
- Every dead end teaches the next move. If an error can be hit by a wrong
|
|
79
|
+
guess, the message must contain the right guess.
|
|
80
|
+
- Signposts name only what exists *here* — same server, same mode, same
|
|
81
|
+
policy — or include the answer inline.
|
|
82
|
+
- Deliver concrete URIs in results; never rely on the agent enumerating.
|
|
83
|
+
- Test discovery the only way that counts: a cold agent, a real task, no
|
|
84
|
+
hints. If it needs a human to say "there's a resource for that," the
|
|
85
|
+
pointer mesh has a hole.
|
|
86
|
+
|
|
87
|
+
Everything above was verified against live agents (Claude Code cold
|
|
88
|
+
sessions for the MCP mode; claude-sonnet-5, an OpenAI model, and a
|
|
89
|
+
LangGraph ReAct agent for embedded mode) — the failures described are
|
|
90
|
+
transcripts, not thought experiments.
|
|
@@ -22,7 +22,10 @@ Name the real boundaries early so the code does not collapse into glue:
|
|
|
22
22
|
- `Schema`: how the agent learns to call something.
|
|
23
23
|
- `Backend`: where code runs.
|
|
24
24
|
- `Bridge`: how sandboxed code calls host capabilities.
|
|
25
|
-
- `Session`: execution state across snippets.
|
|
25
|
+
- `Session`: execution state across snippets. Realized inside the monty
|
|
26
|
+
backend (persistent namespace, on by default — see the
|
|
27
|
+
[session spike](monty-session-spike.md) and `[session]` config) rather
|
|
28
|
+
than as a separate layer: the interpreter that holds the state owns it.
|
|
26
29
|
|
|
27
30
|
The main rule:
|
|
28
31
|
|
|
@@ -181,7 +184,8 @@ src/toolplane/
|
|
|
181
184
|
render.py # brief/detailed/full schema rendering
|
|
182
185
|
json_schema.py # introspection helpers
|
|
183
186
|
|
|
184
|
-
sessions.py
|
|
187
|
+
# (no sessions.py: session state lives in backends/monty.py — the
|
|
188
|
+
# interpreter that holds the namespace owns it)
|
|
185
189
|
errors.py
|
|
186
190
|
```
|
|
187
191
|
|
|
@@ -10,9 +10,13 @@ discover capabilities -> inspect schemas -> execute Python against a curated nam
|
|
|
10
10
|
|
|
11
11
|
Monty is the default backend: safe tool orchestration with no external
|
|
12
12
|
dependencies (see the [decision record](monty-default-spike.md)).
|
|
13
|
-
Pyodide+Deno is the opt-in sandbox for pandas/NumPy-style snippets
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
Pyodide+Deno is the opt-in sandbox for pandas/NumPy-style snippets —
|
|
14
|
+
shipped and **feature-frozen** as of 2026-07: supported as-is, but no
|
|
15
|
+
further investment in its edges; workloads beyond it are served by a
|
|
16
|
+
container backend when a concrete user needs one. Docker/Modal/E2B-class
|
|
17
|
+
backends (arbitrary CPython packages, subprocesses, native dependencies,
|
|
18
|
+
GPUs, remote isolation) stay design-only until then; the material below
|
|
19
|
+
about them is a design record, not a commitment.
|
|
16
20
|
|
|
17
21
|
The execution backend should be swappable. Different workloads need different
|
|
18
22
|
tradeoffs between safety, package support, startup latency, local development
|