Python Runtime, Workflows, Memory, and MCP

May 18, 2026 ยท View on GitHub

This page keeps the Python and agent-facing runtime details that used to live in the root README.

Python Runtime Client

Use the Python runtime client against a running daemon:

from peekaboox.agent import AgentRuntime
from peekaboox.client import Rect
from peekaboox.security import (
    CapabilityProfile,
    ConfirmationPolicy,
    DangerousAction,
)

runtime = AgentRuntime.connect(
    capability_profile=CapabilityProfile.ASSIST,
    confirmation_policy=ConfirmationPolicy.require_for([DangerousAction.CLICK]),
    audit_log_path="peekaboox-runtime-audit.jsonl",
    preflight_mode="strict",
    grpc_token="secret",
)
print(runtime.list_windows())
print(runtime.list_windows(focused=True, limit=1, sort="focused"))
print(runtime.list_windows_result(app="calculator", diagnose=True))
print(runtime.doctor().status)
print(runtime.doctor().categories)
print(runtime.preflight(["desktop", "capture"], operation="capture_screen"))
print(runtime.find_element("role=push button"))
print(runtime.ocr_screen().text)
print(runtime.capture_screen(app="calculator", title_regex="Calculator").metadata)
print(
    runtime.capture_delta(
        stream_id="agent-loop",
        region=Rect(x=10, y=20, width=400, height=240),
    ).changed_bounds
)
print(runtime.capture_backends(output="screen.png", diagnose=True, probe="frame").probes)
print(
    runtime.compare_image_files(
        "before.png",
        "after.png",
        max_changed_ratio=0.01,
        ignore_regions=[Rect(x=10, y=20, width=80, height=24)],
    ).matches
)
print(
    runtime.detect_ui_state_from_image_files(
        ["frame1.png", "frame2.png"],
        ignore_regions=[Rect(x=10, y=20, width=80, height=24)],
        stable_max_changed_pixels=20,
    ).state
)
print(
    runtime.detect_ui_elements_from_image_file(
        "screenshot.png",
        ignore_regions=[Rect(x=10, y=20, width=80, height=24)],
        min_confidence=0.8,
        sort="confidence",
        overlay_output_path="vision-elements-overlay.png",
    ).elements
)
print(runtime.desktop_locate("telegram", "search-input"))
focus = runtime.desktop_focus("telegram", verify=True)
print(focus.focus_diagnostics)
runtime.desktop_click("telegram", "search-input", dry_run=True)
runtime.desktop_type_into("telegram", "message-input", "PeekabooX", dry_run=True)
runtime.click_selector("role=push button,label=Submit", vision_fallback=True)
runtime.click(region=Rect(x=0, y=0, width=400, height=240), ratio_x=0.5, ratio_y=0.5, dry_run=True)
runtime.move_mouse(100, 200)
runtime.move_mouse(window_title="Calculator", ratio_x=0.5, ratio_y=0.5, dry_run=True)
runtime.drag(100, 200, 320, 240, duration_ms=350)
runtime.drag(from_current=True, to_ratio=(0.8, 0.5), region=Rect(0, 0, 400, 240), dry_run=True)
runtime.hotkey(
    "control+s",
    dry_run=True,
    backend="auto",
    delay_ms=25,
    key_delay_ms=30,
    repeat=2,
    interval_ms=40,
    release_before=True,
    release_after=True,
)

Live desktop helper actions focus the target app/window before resolving target coordinates. dry_run=True and offline image-based calls skip that focus step so preview and screenshot-analysis workflows stay side-effect free.

The Python runtime and MCP tool surface share granular capability profiles: observe, plan, assist, and operator. Denied capabilities raise CapabilityDeniedError in Python and return MCP tool errors for JSON-RPC callers, with in-memory audit events available through runtime.capability_audit(). The daemon's separate --profile operator or --allow-input gate still controls real input injection.

An optional ConfirmationPolicy can require application-provided confirmation before dangerous click, type_text, paste_text, or execute_workflow operations. Pointer movement, drags, and hotkeys use the click confirmation gate. Decisions are available through runtime.confirmation_audit(). Pass audit_log_path or run peekaboox-mcp --audit-log <path> to persist runtime security checks as JSONL.

Set preflight_mode="strict" or PEEKABOOX_PREFLIGHT_MODE=strict to run Doctor-backed preflight checks before live desktop, capture, input, and OCR actions. The runtime caches the latest Doctor result, treats warn categories as usable but visible in PreflightResult.warning_categories, and blocks fail or missing categories with PreflightError. Use preflight_mode="warn" to collect the same diagnostics without blocking, or call runtime.require_preflight(["desktop", "input"], operation="click") manually before a custom action. Preflight decisions are available through runtime.preflight_audit() and are persisted as JSONL preflight events when an audit logger is configured.

Workflows

The runtime has a deterministic workflow execution loop. WorkflowStep actions such as find_element, click, move_mouse, drag, hotkey, type_text, paste_text, and observe are retried according to AgentRuntime.retries, verified after execution, and return structured attempt and recovery metadata:

from peekaboox.workflows import Workflow, WorkflowStep

workflow = Workflow(
    name="submit",
    steps=[
        WorkflowStep(action="find_element", selector="role=push button,label=Submit"),
        WorkflowStep(action="click", selector="role=push button,label=Submit", vision_fallback=True),
    ],
)
result = runtime.execute_workflow(workflow)
print(result.ok, result.recovery)

Editable workflow drafts can be generated from a goal. When a fresh semantic desktop graph is available, the generator uses it to produce stronger selectors:

runtime.ingest_desktop_snapshot()
draft = runtime.generate_workflow("Click Submit and type 'Hello'")
runtime.save_generated_workflow("Click Submit and type 'Hello'", "generated.yaml")

Projects can attach a structured refinement provider to PlanningEngine. The provider may improve a draft, but PeekabooX only accepts returned Workflow objects or JSON/YAML workflow definitions that validate as supported WorkflowStep sequences. A separate replanning provider can return a validated replacement workflow after execute_goal fails:

refined = runtime.refine_workflow("Click Submit and type 'Hello'")
runtime.save_refined_workflow("Click Submit and type 'Hello'", "refined.yaml")

During replay, selector-based find_element and click steps self-heal across retries. After an initial selector failure, the runtime refreshes the semantic desktop graph; on a later retry it enables vision_fallback if the step did not already request it. Step results report the applied recovery strategies. When runtime preflight is strict, workflows are checked as a whole before the first step. Missing required Doctor categories return a failed WorkflowExecutionResult with recovery["preflight"] and next_action set to run_doctor.

Workflows can also be loaded from JSON or YAML files. The checked-in examples/workflow.yaml uses the same WorkflowStep fields as the Python API:

result = runtime.execute_workflow_file("examples/workflow.yaml")
print(result.ok)

Interactive actions can be recorded into the same workflow format and exported as JSON or YAML:

runtime.start_recording("manual-submit")
runtime.find_element("role=push button,label=Submit")
runtime.click_selector("role=push button,label=Submit", vision_fallback=True)
runtime.type_text("Hello", typing_speed_chars_per_second=20, backend="wtype")
runtime.save_recording("recordings/manual-submit.yaml")

Workflow files can be validated, replayed, and exported as reproducible bundles through the Python CLI:

peekaboox-agent workflow schema
peekaboox-agent workflow templates
peekaboox-agent workflow template semantic-click --format yaml
peekaboox-agent workflow validate examples/workflow.yaml
peekaboox-agent workflow replay examples/workflow.yaml
peekaboox-agent workflow bundle examples/workflow.yaml --out target/workflow-bundle
peekaboox-agent workflow bundle examples/workflow.yaml --execute

Workflow files are versioned with schema_version: peekaboox.workflow.v1. Missing legacy versions are migrated on load, while unknown top-level or step keys and unsupported actions are rejected before replay. workflow schema prints the JSON Schema used by examples, agents, and CI. A bundle contains normalized JSON/YAML workflow files, workflow.schema.json, metadata, optional doctor.json, and optional replay-result.json. This gives examples and bug reports a stable artifact without requiring prose log parsing. workflow templates lists built-in templates for observe, semantic click, text input, desktop profiles, OCR, visual regression, UI-state waiting, and plugin calls. workflow template <id> prints a ready-to-edit JSON/YAML draft.

find_element also accepts daemon-scoped element lookup fields such as app, window_title, window_id, and the vision_* fallback detector tuning arguments. Scoped or vision-tuned lookups bypass stale graph cache hits and go to the daemon so the requested window and detector options are honored. Text input steps preserve typing_speed_chars_per_second, delay_ms, key_delay_ms, backend, and dry_run through recording and replay. Paste steps preserve preserve_clipboard, dry_run, clipboard_backend, hotkey_backend, delay_ms, restore_delay_ms, and restore_policy. Hotkey steps preserve normalized key chords plus dry_run, backend, delay_ms, key_delay_ms, repeat, interval_ms, release_before, and release_after. The workflow action surface also includes ocr_screen, compare_images, detect_ui_state, detect_ui_elements, desktop_focus, desktop_locate, desktop_click, desktop_drag, desktop_type_into, desktop_assert, plugin_call, wait, and assert_text.

When recording coordinate clicks, the runtime samples semantic desktop state if needed and stores a stable selector such as role=push button,label=Submit when the clicked point resolves to a unique element. Replay can then use the element's current bounds instead of the original click coordinates.

Semantic Desktop Graph

The runtime keeps a semantic desktop graph in memory. A desktop state snapshot turns windows, UI elements, and containment relationships into a queryable graph. Use SQLiteMemoryStore or AgentRuntime.connect(memory_path=...) to persist memory values and graph snapshots across runs:

from peekaboox.memory import SQLiteMemoryStore

runtime = AgentRuntime.connect(memory_path="peekaboox-memory.sqlite3")
snapshot = runtime.ingest_desktop_snapshot()
print(snapshot.active_window_id)
print(runtime.query_desktop_graph(kind="element", label_contains="submit", contained_by="window-1"))
graph_json = runtime.memory.export_desktop_graph()

Desktop events can invalidate or refresh that graph. Events without a fresh state mark the graph stale; refresh_if_stale=True samples the daemon before serving a query:

runtime.record_desktop_event(kind="window.focused", source="accessibility", target_id="window-1")
print(runtime.desktop_graph_status().stale)
print(runtime.query_desktop_graph(kind="element", refresh_if_stale=True))

Fresh graph snapshots are also used as a semantic lookup cache. find_element and semantic click_selector first match selectors against cached graph elements, and only fall back to daemon semantic lookup when the graph is stale or has no match. Use runtime.compact_desktop_graph(max_snapshots=...) or max_age_ms=... to drop old graph snapshots. desktop_graph_status() reports snapshot, node, and edge counts so long-running agents can monitor memory growth.

MCP Server

peekaboox-mcp exposes a concrete MCP-style tool registry and dispatcher over the Python runtime. Run it as a stdio MCP server after installing the Python package, or directly from the checkout during development:

PYTHONPATH=python/src python3 -m peekaboox.mcp.server --list-tools
PYTHONPATH=python/src python3 -m peekaboox.mcp.server
PYTHONPATH=python/src python3 -m peekaboox.mcp.server --audit-log runtime-audit.jsonl
PYTHONPATH=python/src python3 -m peekaboox.mcp.server --capability-profile observe
PYTHONPATH=python/src python3 -m peekaboox.mcp.server --preflight-mode strict
PYTHONPATH=python/src python3 -m peekaboox.mcp.server --grpc-token secret
PYTHONPATH=python/src python3 -m peekaboox.mcp.server --transport http --host 127.0.0.1 --port 47778 --auth-token secret
PYTHONPATH=python/src python3 -m peekaboox.mcp.server --transport sse --host 127.0.0.1 --port 47778 --auth-token secret

Tool execution through MCP requires Python runtime dependencies and a running peekabooxd reachable at PEEKABOOX_GRPC_TARGET or --target. Without those dependencies, the server can still list tool descriptors for inspection. When the daemon is started with --grpc-token, set the same token through --grpc-token or PEEKABOOX_GRPC_TOKEN; the runtime sends it as gRPC metadata on every daemon call. The server also exposes MCP resources, resource templates, prompts, completion, and logging level negotiation alongside the tool registry. --transport stdio is the default for MCP clients; --transport http serves JSON-RPC POST requests on /mcp; --transport sse additionally exposes a lightweight /sse endpoint that advertises the JSON-RPC endpoint and tool list for clients that discover servers through server-sent events. Workflow template resources are available at peekaboox://workflows/templates and peekaboox://workflows/templates/{template_id}. Set --auth-token or PEEKABOOX_MCP_TOKEN for HTTP/SSE clients; non-loopback HTTP/SSE hosts are refused unless a token is configured. HTTP clients may send Authorization: Bearer <token> or X-PeekabooX-MCP-Token. The default request body limit is 1 MiB and can be changed with --max-request-bytes or PEEKABOOX_MCP_MAX_REQUEST_BYTES. Use --preflight-mode off|warn|strict and --preflight-timeout <seconds> to control Doctor-backed preflight gates without changing application code. When preflight blocks an MCP tool call, the tool result keeps isError: true and includes blocked_categories, warning_categories, next_action, and the full preflight object in structuredContent. Run examples/mcp/jsonrpc_preflight_error_client.sh for a deterministic client-side example that turns those fields into a run_doctor recovery action.

The current tool surface includes capture, capture delta, DMA-BUF probe, click, text and paste input, semantic lookup, window listing, desktop state, desktop app-target tools, OCR, visual diff, UI-state and UI-element detection, plugin discovery/execution, semantic desktop graph ingestion/querying, workflow generation/refinement/replanning/execution, workflow recording tools, runtime audit tools, and CLI-compatible aliases such as elements, ocr, vision_elements, and capture_dmabuf. Run examples/python/capture_dmabuf_runtime.py to validate the Python runtime DMA-BUF result mapping without requiring a live PipeWire DMA-BUF session, and examples/mcp/jsonrpc_capture_dmabuf.sh to validate the MCP probe_dmabuf/capture_dmabuf schemas with an optional live daemon call. list_windows accepts the same filtering and diagnostics fields as the daemon CLI: id, app, title, title_regex, focused, limit, sort, backend, and diagnose. Resources under peekaboox://server, peekaboox://desktop, peekaboox://audit, and peekaboox://docs expose server metadata, desktop profile data, latest graph/preflight state, audit events, and documentation. Prompts cover diagnosis, safe actions, workflow generation, structured error recovery, plugin development, OCR, and semantic click planning.

For local inspection without an MCP client:

peekaboox-agent --version
peekaboox-agent plugins --path examples/plugins
peekaboox-agent preflight desktop capture --operation capture_screen
peekaboox-agent windows
peekaboox-agent --preflight-mode strict windows
peekaboox-agent windows --focused --limit 1 --sort focused
peekaboox-agent windows --app calculator --diagnose
peekaboox-agent desktop-state

Safety Notes

By default, daemon-routed real input injection is denied. Use peekabooxd run --profile operator, --allow-input, or PEEKABOOX_ALLOW_INPUT=1 only for trusted local automation sessions. Audit logs are written as JSONL; see docs/security.md.

Use peekabooxd run --sandbox basic for in-process Linux hardening, or install integrations/systemd/peekabooxd-hardened.service for a stricter observe-only systemd sandbox.

peekabooxd also starts a best-effort CTRL + ALT + ESC emergency hotkey listener. When readable Linux input devices are available, the hotkey shuts the daemon down and releases common modifier keys. Use --no-emergency-hotkey or PEEKABOOX_EMERGENCY_HOTKEY=0 in environments where /dev/input/event* access is not available or not desired.