Metadata-Version: 2.4
Name: operandi-sdk
Version: 0.1.0
Summary: Let a robot operate any appliance — pull a verified operation package and execute it safely.
Project-URL: Homepage, https://operandi.cc
Project-URL: Docs, https://operandi.cc/docs/
License: Proprietary
Keywords: appliances,embodied-ai,home-robot,manipulation,robotics
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# OPERANDI SDK — let your robot operate any appliance

Your robot can walk, see, and grasp. It still can't operate the microwave, the
washing machine, the oven, the coffee machine — because it doesn't know *how*.
OPERANDI is that missing layer. This SDK is the module you drop into your robot's
software to access appliances that were previously inaccessible.

```python
from operandi_sdk import OperandiClient, execute
from my_robot import MyRobotAdapter          # you implement ~6 primitive skills

client = OperandiClient("https://api.operandi.cc", api_key="ok_live_…")

match = client.identify("Bosch Series 6 dishwasher SMS6ZCI00G")   # what your camera read
pkg   = client.operate(match.slug, robot_profile="so101")          # verified operation package
result = execute(pkg, robot=MyRobotAdapter())                      # SDK runs it, safely

print(result.summary())     # COMPLETED · 7 steps · package=sim_validated
```

## What you get vs what you implement
| OPERANDI provides | You implement |
|---|---|
| The **knowledge**: every control + its physical interaction | **Actuation** — your ~6 primitive skills |
| **Grounding — *where*:** each control's OCR `text_targets`, `region`, shape `primitive`; `localize()` returns a **bbox per control** from a panel photo | a camera + OCR (or your own detector) |
| **Grounding — *did it work*:** each step's `signal` + expected reading `expect`; `evaluate()` decides pass/fail | `read_sensors()` — raw readings (display text, door, lamps…) |
| The **plan**: ordered procedures as a behaviour tree | — |
| The **safety envelope**: force limits, never-do, interlocks | (the SDK attaches the force limit to every call) |
| **Orchestration**: sequencing, retries, safe abort | — |

The package no longer hands you prose like *"pad labelled START"* and *"display shows
5:00"* — it hands you the **exact OCR string to find** (`text_targets: ["START"]`) and
the **machine-checkable expectation** (`signal: "display", expect: "5:00"`). So your
adapter is small:

```python
from operandi_sdk import RobotAdapter, SensorPerceptionMixin, SkillCall, Observation, localize

class MyRobotAdapter(SensorPerceptionMixin, RobotAdapter):
    def execute_skill(self, call: SkillCall) -> bool:
        # call.anchor carries: text_targets (OCR strings), region, primitive (shape), spatial
        # call.force_limit: the safety cap, e.g. "<=8N"  (respect it)
        boxes = localize(self.camera.ocr(), [call.anchor])     # OCR-grounded bbox per control
        pose  = self.depth.pose(boxes.get(call.control_id))    # your 2D->3D (or use the detector)
        return self.arm.actuate(call.skill, pose, max_force=call.force_limit, **call.params)

    def read_sensors(self, predicate) -> Observation:          # implement ONCE; verifies every step
        return Observation(display_text=self.camera.read_display(),
                           door=self.camera.door_state(),
                           moving=self.camera.motion())
        # the SDK evaluates predicate.expect against these — no per-appliance check code
```

Prefer full control? Implement `observe(predicate_id, predicate)` yourself instead of
the mixin, or override per-kind `check_display_change` / `check_door_state` (see
`PerceptionMixin`). The mixin is just the fast path.

## Try it with no hardware
```bash
pip install -e .
python example.py             # identify -> operate -> execute, against a fixture
python example_grounding.py   # PROVES grounding: localize controls from a panel photo,
                              # then verify every step against (simulated) sensor readings
```

## Safety contract
- Every actuation carries a `force_limit` derived from the appliance's safety
  envelope — **respect it** (the manual's interlocks become your hard limits).
- Verification is **closed-loop**: each step's success-signal is polled over its
  timeout, and if it still doesn't appear the SDK **re-actuates** the step (a press
  that didn't register → press again) up to `verify_retries` times — implement the
  optional `on_verify_retry(call, predicate, attempt)` hook to adapt between tries.
  If it ultimately can't verify (or a skill faults), the SDK aborts the whole
  procedure and calls `on_safety_abort()` so you can e-stop / retract.
- A package's `validation_tier` (`auto` → `sim_validated` → `robot_cleared`) tells
  you how much trust it has earned. Gate hardware execution on your own policy.

## Zero dependencies
Stdlib only — no `requests`, no heavy ML — so it fits constrained on-robot runtimes.
