Python Execution Service#
The Python Execution Service runs user-provided code in an isolated
environment with safety checks, process isolation, and timeout enforcement.
The Osprey agent uses it via the execute MCP tool to perform data analysis,
plotting, and control-system interactions on behalf of the operator.
What It Does#
The service accepts Python source code, applies layered safety checks, and
runs it as a subprocess on the host (ExecutionWrapper).
Results—stdout, stderr, figures, and saved artifacts—are returned as
structured JSON.
Osprey agent → execute MCP tool → safety checks → host subprocess → result JSON
Executed code can import whatever is installed in the project’s environment;
see The Execution Environment for how to find out what that is.
A save_artifact(obj, title="Untitled", description="", artifact_type=None, category="")
helper is injected into the subprocess namespace for saving objects to the
artifact gallery. The artifact_type parameter overrides automatic type
detection (e.g., "figure", "dataframe"); category is a free-form
grouping label used by the gallery UI.
MCP Tool Interface#
The server exposes two tools, execute and execute_file, registered on
the python FastMCP server (osprey.mcp_server.python_executor).
execute_file runs an existing .py file on disk through the same safety
pipeline as execute; both share the parameters below (execute_file
takes file_path and optional script_args in place of code).
Parameter |
Default |
Description |
|---|---|---|
|
(required) |
Python source code to run. |
|
(required) |
Human-readable description of what the code does. |
|
|
|
|
|
Save code and output to a workspace data file and artifact store. |
On success the tool returns a JSON object whose top level describes the saved run:
status (
"success"), artifact_id, title, and data_file for the saved artifact.summary — a nested object with the run details: the truncated output / error (500 characters; full output is in the saved data file), a
"Success"/"Failed"status, has_errors, and detected_patterns for control-system operation metadata.artifact_ids — IDs for any figures or saved objects, and notebook_artifact_id for the auto-generated notebook capturing the run.
gallery_url — link to the artifact gallery (when available).
A failing run does not return this object as a normal result: the tool raises
a structured error (error_type: "execution_error") whose details field
carries the same information, so the agent receives an explicit error rather
than a status field it might overlook.
Where Code Runs#
Agent-authored Python runs as a subprocess on the same host as the Osprey agent. This is the only execution backend, so there is nothing to choose between:
# config.yml
execution:
execution_method: subprocess
subprocess is the default and the key can be left out entirely. Two older
values still load, so existing projects keep working: local is the former
name for this same backend and is accepted silently, and container is
treated as subprocess and logs a one-time warning naming the config file it
came from. Any other value is a configuration error.
The ExecutionWrapper wraps user code with safety monkeypatches (e.g.
epics.caput() validation against the limits database), writes the wrapped
script to an execution folder, and runs it. The subprocess working directory is
set to the project root so that relative workspace paths (e.g.
_agent_data/data/002_archiver_read.json) resolve correctly.
The Execution Environment#
Executed code runs in the project’s own virtual environment
(<project>/.venv) when the project has one, and otherwise in the
interpreter running Osprey. config.yml records no interpreter path and
offers no setting to point execution somewhere else — which environment
exists is decided when the project is built, from the build profile’s
environment: block (see Build Profiles).
Anything installed in that environment is importable by executed code:
cd my-project
uv pip list # what executed code can import
uv pip install lmfit # importable by the next execution
The description the agent sees for the execute tool is generated from this
environment rather than from a fixed list, so the agent is told what is really
installed. It is computed once when the MCP server starts: a package installed
mid-session is importable straight away, but the agent will not know about it
until the next session. If the environment cannot be read at startup, the
description names no packages at all instead of guessing.
A container image built from the project installs the same package set as the project’s own environment, so executed code sees the same imports either way.
Security Model#
Five safety layers are applied in sequence:
Static safety check (
quick_safety_check)—blocks dangerous patterns such as dynamic code evaluation, dynamic imports, andsubprocesscalls before execution begins.Control-system pattern detection (
detect_control_system_operations)—identifies read and write patterns. Inreadonlymode, detected writes cause immediate rejection.Limits monkeypatch (
ExecutionWrapper/LimitsValidator)—at runtime,epics.caput()calls are intercepted and validated against the channel limits database. Out-of-range values are blocked.Process isolation—code always runs in a separate subprocess, never inside the MCP server process.
Execution timeout—configurable via
python_executor.execution_timeout_seconds(default 600 s). The process is killed if it exceeds the limit.
python_executor:
execution_timeout_seconds: 300
Control system operations in user code
Python code interacts with control systems using
osprey.runtime utilities (read_channel(), write_channel()),
not direct connector imports. The execution wrapper configures these
automatically from the deployment context, so code works with any
connector (EPICS, Mock, etc.) and notebooks remain reproducible.
Note
There is no in-framework code-generation pipeline. The Osprey agent generates
Python code itself and invokes the execute MCP tool directly.
Note
Write approval is handled by the execution_mode parameter. The Osprey agent
requests user confirmation before calling execute with
execution_mode="readwrite"—there is no separate approval API.
Installation#
The Python executor is included in the default Osprey installation:
uv sync
No additional setup is needed.
See Also#
MCP Servers for how the
pythonserver fits into the overall system.src/osprey/mcp_server/python_executor/for the full server source.src/osprey/services/python_executor/for execution engine internals, safety checks, and pattern detection.