01 · Install the core
The distribution name is percept-vision; the Python import is percept. The package imports cleanly with no key and nothing extra installed — vendor SDKs are lazy-imported only inside adapter methods.
python3 -m venv .venv
source .venv/bin/activate
pip install percept-vision
python -c "import percept"Preview: the WATCH_VERSION 1.1.0 / WatchPlan surface on this page is the version pinned for Percept Cloud. The public PyPI package can lag it — until a matching wheel is published, pin the version your build targets and treat these snippets as the Cloud-pinned preview rather than a released API. See release status.
02 · The watch() one-liner
watch() is the frozen high-level entrypoint (WATCH_VERSION = "1.1.0"). Goals are plain-language strings, Goal objects, WatchPlans, or any mix — the condition is carried verbatim to the judge, never compiled to a rigid predicate. A frame that changes nothing yields zero events; one WatchEvent is yielded per gate FIRE or ASK.
import percept
async for event in percept.watch(stream, "the kettle boils"):
handle(event) # WatchEvent — one per gate FIRE or ASK; silent frames yield nothing03 · Offline, deterministic, no API keys
The deterministic fakes (FakeVision, FakeConductor, FakeSTT, FakeTTS) call no network and need no credential — the whole cognition path runs on your machine.
from percept import Percept
agent = Percept.create(mode="offline") # scripted/fake seams — no keys, no network
verdict = await agent.perceive_judged(frame)The safety posture is loud, not silent: in LIVE mode an unset vision backend — or a real backend with its credential missing — raises a ModeError naming the exact env var to set. You can never silently land on a fake and mistake it for success.
04 · percept.run and RuntimeSession
percept.run(...) is the batteries-included front door: it opens a video or audio source, drives the cognition core over frames, and delivers durable incidents — idempotent, deduped by incident id. percept.start(...) returns a RuntimeSession with the interactive inputs: ask, interrupt, arm/disarm, pause/resume, label.
import percept
async for incident in percept.run(
source="kitchen.mp4", # camera index, file, or RTSP — or any frame iterable
goals=["the milk is boiling over"],
fps=4.0,
on_ask=handle_ask, # low-confidence refusals — asks are not incidents
):
notify(incident) # a durable Incident, deduped on redelivery05 · Real providers, when you opt in
Frontier backends (Anthropic, Gemini, Deepgram, OpenRouter) sit behind the vendor-neutral Vision seam as opt-in extras. The rule is the identity/configuration split: the provider name is the adapter's identity; the model id is configuration carried to it, never its identity.
# Provider name is identity; the model id is configuration. resolve_vision("claude", model=...) # Anthropic adapter — needs its key + extra resolve_vision("gemini", model=...) resolve_vision("openrouter", model=...) resolve_vision("scripted") # deterministic fake — no key, no network
06 · Version contracts
WATCH_VERSION = "1.1.0"— the frozenwatch()entrypoint; plans and goals arm through it and earn aWatchReceiptwith per-field diagnostics.CONTRACT_VERSION = "1.10.0"— the shared data contract. A breaking change to a frozen shape is a MAJOR bump, never a silent edit;negotiate()raisesContractVersionMismatchrather than guessing.