onelastleaf Python plugin SDK
This repository contains the asyncio/gRPC runtime used by Python plugins for
onelastleaf. The package name is
onelastleaf-plugin-sdk; Python code imports it as
onelastleaf_plugin_sdk.
If you are here to try the SDK, there are two useful paths:
- work on and test the SDK itself;
- generate a small Python plugin and let oll install and run it.
Both are covered below. Python 3.11 or newer is required. The SDK is tested on Linux, macOS, and Windows with Python 3.11 through 3.14.
Install the SDK
Install the published release inside your project's virtual environment:
python -m pip install onelastleaf-plugin-sdk==0.1.0
Most plugin authors do not need to install the SDK by hand: the project
generated by oll plugin new already declares this exact dependency.
Work on the SDK
Create a development environment
Keep the SDK's dependencies in a virtual environment inside this checkout:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e '.[dev]'
-e . installs the checkout in editable mode, so source changes are visible
without reinstalling it. The dev extra adds the test, lint, type-check,
package-build, and pinned protobuf-generation tools; plugin users do not install
those tools.
If python3 -m venv cannot create an environment, install your operating
system's Python venv/ensurepip package first. You should not need sudo pip or
an installation into the system Python.
Run the tests
With the environment still active:
python -m pytest
ruff format --check src tests tools
ruff check src tests tools
mypy
python tools/generate_protocol.py --check
The tests cover handshake validation, full trace propagation, concurrent job completion/cancellation races, late host responses, stdin liveness, and bounded-memory artifact streaming. The other commands check formatting and typing and prove that the checked-in protocol bindings are reproducible.
Build a wheel and source archive
Python does not have a separate compile step for this SDK. The closest release build is:
python -m build
This writes a wheel and source archive to dist/. Building is not required
while developing with an editable install.
Sync and regenerate the protocol
The main onelastleaf repository is the protocol authority. Pull the current files from GitHub rather than relying on another checkout that happens to be on your machine:
protocol_source="$(mktemp -d)"
git clone --depth 1 https://github.com/onelastleaf/onelastleaf.git \
"$protocol_source/onelastleaf"
for name in common config document plugin; do
cp "$protocol_source/onelastleaf/proto/oll/$name.proto" proto/oll/
done
python tools/generate_protocol.py
python tools/generate_protocol.py --check
grpcio-tools==1.75.1 is pinned in the development extra. Its generated gRPC
guard therefore agrees with the runtime dependency floor grpcio>=1.75.1, and
its protobuf generator agrees with protobuf>=6.31.1. Only
src/onelastleaf_plugin_sdk/protocol/ is generated; there is no second output
tree to keep in sync. CI runs Python 3.11 through 3.14 against those exact
runtime floors, so the declared minimums are exercised rather than merely
accepted by the resolver.
Try the SDK with oll
Install the matching released oll binary and put it on PATH, then follow the
main project's quick start
to initialize and start the local daemon. You will also need a remote Git
repository that the daemon can clone.
A plugin cannot be run by pointing it at a public oll port: oll creates a private loopback gRPC endpoint for each plugin process.
1. Generate a Python plugin
Choose a new destination and stable plugin ID:
oll plugin new hello-python \
--language python \
--id dev.example.hello \
--name hello-python
cd hello-python
Generation only writes files. It does not create a venv, install packages,
initialize Git, or contact the daemon. The generated project contains an
echo action, a test, pyproject.toml, and the publisher manifest oll.toml.
2. Install the published SDK dependency
The generated pyproject.toml pins the published SDK release:
dependencies = ["onelastleaf-plugin-sdk==0.1.0"]
Leave this dependency as published. During local plugin development, pip
downloads it from the configured Python package index. During plugin
installation, oll creates a fresh managed venv and the generated source recipe
downloads the same pinned release there.
3. Test the plugin as a normal Python project
The plugin gets its own development environment. It is separate from the SDK
checkout's .venv:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e . pytest
python -m pytest
deactivate
Do not start the generated console script directly as an end-to-end test. The
script expects OLL_PLUGIN_ENDPOINT and a parent-liveness stdin pipe, both of
which are supplied by oll when it supervises the process.
4. Commit, install, and call the plugin
oll installs source plugins from Git, so commit the project and push it to a remote the daemon can access:
git init -b main
git add .
git commit -m "feat: add hello Python plugin"
git remote add origin https://github.com/you/hello-python.git
git push -u origin main
Then install and start it:
oll plugin install https://github.com/you/hello-python.git --source
oll plugin start dev.example.hello
oll plugin info dev.example.hello
Call the generated action:
oll plugin call dev.example.hello echo -- hello from python
oll job info <job-id-printed-by-the-previous-command>
plugin call returns after the job is admitted and prints its job ID. Use
job info to inspect the result. Runtime output is available through:
oll plugin log dev.example.hello
After changing the plugin, commit and push again, then publish the new generation and restart the running process:
oll plugin update dev.example.hello
oll plugin restart dev.example.hello
A successful update deliberately does not restart a running plugin for you.
Why there are several virtual environments
Python venvs can feel confusing here because they solve three different problems:
| Environment | Owner | Purpose |
|---|---|---|
SDK checkout .venv |
you | Run this repository's tests and build packages. |
Plugin checkout .venv |
you | Run the plugin's own tests while developing it. |
Installed generation .venv |
oll | Run the exact plugin generation managed by the daemon. |
The first two are disposable local development environments. oll neither reads
nor reuses them. The generated Python oll.toml instead declares this recipe:
[source]
checkout = "generation"
steps = [
["python3", "-m", "venv", "{generation}/.venv"],
["{generation}/.venv/bin/pip", "install", "{generation}"],
]
[runtime]
argv = ["{generation}/.venv/bin/hello-python"]
oll places the complete Git checkout in its final UUID-named generation before
creating the venv. That ordering is important because venv scripts contain
absolute paths and may break if their directory is renamed afterward. Only
after the build is validated does oll atomically switch current to the new
generation; a failed update leaves the old one current. On startup, oll also
cleans up unreferenced generations left behind by a crash.
The installed generation keeps the source checkout, its .git directory, the
venv, and other build output. Do not commit secrets, and expect an installed
Python plugin to use more disk space than its source tree alone.
This checkout mode is selected by the publisher manifest, not guessed from the
language, and a user mask cannot change it. Generation-mode recipes may refer
to {generation} and {mask_dir}; {source} and {install} are not available
in this mode. Release installation ignores the source checkout recipe and still
requires a relocatable published artifact.
Runtime model in plain English
The plugin does not host a server. oll hosts a loopback TCP gRPC server on an
ephemeral port, starts the plugin as a trusted child process, and passes the
address in OLL_PLUGIN_ENDPOINT. The SDK connects to that address, completes
the protocol handshake, and dispatches actions registered with
Plugin.action.
The child's stdin is reserved for parent liveness. EOF means the oll parent is gone, so the SDK exits; stdin is not available for plugin commands or user input. stdout and stderr are captured in per-plugin logs.
A minimal action looks like this:
import asyncio
from onelastleaf_plugin_sdk import ActionResult, Plugin
plugin = Plugin("dev.example.hello", "0.1.0")
@plugin.action("echo", "Return the supplied arguments")
async def echo(_context, arguments: list[str]) -> ActionResult:
return ActionResult.string(" ".join(arguments))
asyncio.run(plugin.run())
The generated project wraps this in a console entry point so oll can start it from the managed venv.
The action context owns a host API already bound to that job and its trace, so
normal plugin code does not pass job_id or trace messages around:
from onelastleaf_plugin_sdk.protocol import common_pb2
configured = await context.host.get_config()
document = await context.host.read_document(request)
await context.host.log(
common_pb2.LOG_LEVEL_INFO,
"my-plugin",
"work completed",
)
Artifacts use a seekable binary source. The SDK hashes it outside the event loop, negotiates the transfer, and sends one bounded chunk at a time:
from io import BytesIO
from onelastleaf_plugin_sdk import ArtifactInput
descriptor = await context.host.store_artifact(
ArtifactInput(
artifact_id="aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
file_name="result.txt",
media_type="text/plain",
source=BytesIO(b"result"),
)
)
context.deadline is informational. oll owns deadline enforcement and sends a
job-scoped cancellation request when it expires. The SDK sets
context.cancellation, cancels only that action task, keeps serving heartbeats
and other jobs, and acknowledges cancellation only after the action has
stopped.
Protocol evolution
This SDK follows the canonical protobuf wire contract. It never computes, embeds, publishes, or compares a schema hash or fingerprint. Descriptor-wide hashes change for compatible additions and unrelated services, so they reject valid peers. Protocol changes instead preserve field numbers and wire types, give additions safe absent semantics, and tolerate unknown fields. Exact SDK pins provide reproducible builds; they are not protobuf API versioning.
The plugin envelope has no SDK-level encoded-size cap. Both gRPC directions are
configured as unlimited so Python's usual 4 MiB receive default is not retained.
Artifact data still follows the smaller chunk size negotiated in HostHello.
Common problems
No module named onelastleaf_plugin_sdk: activate the intended venv and runpython -m pip install -e .in the SDK or plugin checkout as appropriate.OLL_PLUGIN_ENDPOINT is required: the plugin was started by hand. Install and start it through oll for an end-to-end run.- Installation fails while running pip: inspect the diagnostic's retained
build-log path. The daemon user needs
python3with venv support and access to every package source named bypyproject.toml. - New code is installed but old behavior is still running:
plugin updatedoes not restart the process; runoll plugin restart <id-or-name>.
Metadata
Release files for onelastleaf-plugin-sdk 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| onelastleaf_plugin_sdk-0.1.0.tar.gz | 69.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| onelastleaf_plugin_sdk-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 134.4 kB
Release files / onelastleaf_plugin_sdk-0.1.0.tar.gz
| Download URL | onelastleaf_plugin_sdk-0.1.0.tar.gz |
|---|---|
| Size | 69.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ec83febdc332273775f696e63232cc8e0dc46270a66021bff3ee1da8ed883bea
|
|
BLAKE2b-256 checksum How to use checksums |
83f560a7af09f00b39d147f68f737ae485f0d440858963dd71cd2ec8f9b64fe3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Gentoo","version":"2.18","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / onelastleaf_plugin_sdk-0.1.0-py3-none-any.whl
| Download URL | onelastleaf_plugin_sdk-0.1.0-py3-none-any.whl |
|---|---|
| Size | 65.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e33b7a06b48f2a79f3fd6d93d7657419dd1590407d87de4d4b276a4ebbc724ef
|
|
BLAKE2b-256 checksum How to use checksums |
8422e04cd1e8e7ee6bc949e915af007b652b98e1ad0369fd7e050f065754bd9e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Gentoo","version":"2.18","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|