The official pyproc-control Python package starts and controls the installed npm product without
JavaScript application code. The SDK has no runtime dependency and supports Python 3.10 or newer.
The two packages have separate jobs:
- npm
pyprocships the browser runtime,pyproc-controlcommand, policy manifest, and engine tooling. - Python
pyproc-controlships the strict protocol client, typed values, cancellation, and attachment verification.
Pin both packages to the same version. This release uses 0.0.21 for each.
npm install --save-exact pyproc@0.0.21
npx pyproc-engine --out /absolute/path/to/pyodide
python -m pip install \
"https://github.com/eddmpython/pyproc/releases/download/v0.0.21/pyproc_control-0.0.21-py3-none-any.whl"The Python distribution is currently published as wheel and source distribution assets on the matching GitHub Release. PyPI is not an installation source yet. Use the exact-version asset URL because floating release URLs are outside the reproducible installation contract.
Create the version 1 manifest with Machine Entrance. A Python-only recipe expands to
"browser": { "enabled": false }. Run the complete doctor before use:
npx pyproc-mcp init --recipe pythonOnly --engine-root /absolute/path/to/pyodide
npx pyproc-control doctor --config ./.pyproc/manifest.jsonfrom pyprocControl import PyProcClient
report = PyProcClient.check(".pyproc/manifest.json")
print(report["engine"])The npm bin directory must be on PATH. An embedded application can instead pass an explicit command list
such as command=[nodePath, controlScriptPath].
from pyprocControl import PyProcClient
with PyProcClient.start(".pyproc/manifest.json") as client:
client.runPython("prepared = [10, 20, 30]")
checkpoint = client.saveCheckpoint()
try:
client.runPython("prepared.append(999)\nraise ValueError('failed attempt')")
except Exception:
client.restoreCheckpoint(checkpoint.output["index"])
result = client.runPython("prepared")
print(result.output["value"])ControlResult contains terminal="completed", output, outcome, and an ordered tuple of verified
attachments. ControlError.terminal is rejected, partial, outcomeUnknown, or cancelled while preserving
the protocol code, outcome, and retryability. Machine state persists for the life of the client. reset()
restores the prepared boot checkpoint.
Browser operations appear only when the manifest grants automation authority. Risk is fixed by the product catalog and must be acknowledged on each effect:
with PyProcClient.start(".pyproc/manifest.json") as client:
opened = client.openTarget(
"https://example.test",
expectedRisk="externalEffect",
waitUntil="load",
)
attached = client.attachSession(opened.output["targetRef"])
captured = client.act(attached.output, [
{"kind": "screenshot", "format": "png", "expectedRisk": "read"}
])
png = captured.attachments[0]
assert png.mimeType == "image/png"
with open("capture.png", "wb") as file:
file.write(png.bytes)
client.detachSession(attached.output)The client does not expose attachment bytes until chunk order, byte length, MIME type, and SHA-256 match the terminal descriptor. The JSON output retains the opaque artifact reference for bounded reads and deletion.
client.perception(sessionRef) binds provider-neutral APX graph and situation representations to one attached
session. It uses the same
automation.observe and automation.act operations as MCP and native JavaScript clients, so Python does not
gain a second automation meaning or a raw CDP entrance.
with PyProcClient.start(".pyproc/manifest.json") as client:
opened = client.openTarget(
"https://example.test",
expectedRisk="externalEffect",
waitUntil="load",
)
attached = client.attachSession(opened.output["targetRef"])
eyes = client.perception(attached.output)
situation = eyes.situate({"requirements": [{
"requirementRef": "requirement:save",
"select": {"role": "button", "name": "Save", "actionable": True},
"need": ["fact", "affordance"],
"cardinality": "one",
}]})
save = situation.requirement("requirement:save").oneAffordance("click")
result = eyes.actAffordance(
save,
intent="Save the document",
verify={"entityAppeared": {"role": "status", "nameContains": "Saved"}},
)
assert result.output["actions"][0]["result"]["evidence"]["verification"]["state"] == "confirmed"SituationResult exposes immutable requirements, facts, affordances, and unknowns. Only an authorized
SituationAffordance can be passed to actAffordance; page-reported affordances remain content. The older
PerceptionEntity exposes entityRef, locatorRef, kind, role, name, and actionable properties.
query(...).one() checks APX query.matched and rejects zero or multiple matches even when the byte budget
returns only one candidate. whatChanged(observationRef) requests
a delta, while explainActionability(entityRef) narrows the graph to semantic, geometry, and interaction
facts. Observation identity never grants action authority, and locator capability remains short-lived.
Native CDP supports verified pixel-on-demand attachments. FrameSpace supports APX through temporal level L3 but rejects visual inference. See the APX 1.0 product contract for the graph and evidence contract.
requestAsync returns a ControlRequest:
from pyprocControl import ControlError
request = client.requestAsync("machine.run", {
"code": "import time\ntime.sleep(30)"
})
request.cancel("deadline reached")
try:
request.result()
except ControlError as error:
print(error.code, error.outcome, error.retryable)Every synchronous timeout= is an effect-safe request deadline. When the local wait expires, the client sends
one protocol cancel and waits for the canonical terminal. A queued request ends as notSent; a delivered effect
normally ends as non-retryable outcomeUnknown. If the server does not settle after cancellation, the client
closes the connection and raises non-retryable CONTROL_TIMEOUT with outcomeUnknown.
If the transport ends while a request is pending, or a request frame write cannot prove full delivery, the
SDK raises non-retryable CONTROL_CONNECTION_LOST with outcomeUnknown. A request rejected before writing
because the connection is already known to be unavailable is notSent. Raw pipe and EOF errors do not escape
through the request surface.
Cancellation before delivery is notSent. After delivery, the product returns outcomeUnknown unless the
provider proves a narrower boundary. Never retry an applied or outcomeUnknown effect automatically.
Python restore does not undo browser, storage, network, download, popup, or other external effects.
ControlError exposes stable code, outcome, retryable, details, and fatal fields. Protocol framing
or digest violations fail the whole client and reject every outstanding request.
npm run test:python-sdk builds both the wheel and source distribution with pinned build tools, installs
each into a separate clean virtual environment, and runs these installed-package checks:
- strict codec positive and negative fixtures;
- PATH-based product preflight and handshake;
- persistent Python and checkpoint restore;
- post-send cancellation and request ID single-use;
- permission denial before browser effect;
- APX query through both Native CDP and FrameSpace;
- real browser open, attach, PNG capture, SHA-256 verification, artifact deletion, and detach.
Chrome on Ubuntu and Edge on Windows run the same gate in CI.