> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cadenzalabs.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Relational megan (local)

> Drive megan's relation chain in-process: parse an instruction, ground the entities, read guidance each control step.

<Warning>
  **There are two megan surfaces and they are not versions of each other.**

  |               | `megan()` / `MeganTK`                                           | `Megan` (this page)                      |
  | ------------- | --------------------------------------------------------------- | ---------------------------------------- |
  | Where it runs | Hosted Cadenza API, metered                                     | In your process                          |
  | Vocabulary    | Milestones, disturbances, a progress scalar                     | Relations with margins                   |
  | What you send | `reached=[0]`, `progress=0.42`                                  | A camera frame and the robot's state     |
  | What you get  | A frontier, a protective action, an action-frame rotation `phi` | `Guidance` — wake or not, and the remedy |
  | Install       | Nothing beyond stdlib                                           | `pip install cadenzalabs[megan]`         |

  Pick the hosted one for milestone and anticipation work you want metered and
  managed. Pick this one when you need megan reasoning about the relation between
  a gripper and the things it is acting on. The hosted API has **no relational
  routes**, which is why this surface is local — not a preference.
</Warning>

## Install

```bash theme={null}
pip install "cadenzalabs[megan]"
```

That pulls in `megantk` and `numpy`. The core CLI and the hosted client need
neither, and nothing is imported until you touch `Megan`.

```python theme={null}
from cadenzalabs import relational_available
relational_available()      # True/False, never raises
```

## The whole loop

```python theme={null}
import cv2
from cadenzalabs import Megan

m = Megan("pick up the red block")

frame = cv2.imread("scene.png")             # BGR, as OpenCV gives it
m.teach("the red block",    frame, uv=(0.62, 0.40))
m.teach("the robot gripper", frame, uv=(0.48, 0.79))

assert not m.untaught(), m.untaught()       # everything the plan needs

g = m.step(frame, joints=q, proposed=action)
if g.active:
    handle(g)                                # g.kind, g.instruction, g.focus_uv
```

## Instructions must parse

megan plans from a verb with an object, and a destination when something is
being placed:

| Instruction                        | Parses | Chain                               |
| ---------------------------------- | ------ | ----------------------------------- |
| `pick up the red block`            | yes    | `aligned → near → grasped → lifted` |
| `put the red block on the tray`    | yes    | the above, plus `over → placed`     |
| `push the red block to the corner` | yes    | `aligned → near → over`             |
| `follow the red block`             | **no** | —                                   |
| `move the red block`               | **no** | —                                   |

<Warning>
  An unparseable instruction yields **zero subgoals**, so megan has no relation
  to enforce and will never act. That is indistinguishable from megan deciding
  your policy needs no help — the same silent output for opposite reasons. The
  constructor warns, and you can check:

  ```python theme={null}
  m = Megan("follow the red block")   # UserWarning: could not parse
  m.parsed                            # False
  m.subgoals                          # ()
  ```

  Pass `warn=False` to silence the warning; `parsed` still tells the truth.
</Warning>

## Teach every entity, including the gripper

`aligned`, `near` and `over` are relations **between** the hand and an object.
With no prototype for the hand they cannot be evaluated, megan reports `blind`
on every frame, and you see no guidance at all.

```python theme={null}
m.entities
# {'the robot gripper': 'gripper', 'the red block': 'graspable'}

m.untaught()
# ('the robot gripper', 'the red block')   <- both, before teaching
```

Teach from whatever your perception already gives you — a click, a colour blob
centroid, a detector box. One point in one frame is enough:

```python theme={null}
m.teach("the red block", frame, uv=(0.62, 0.40))      # normalised 0–1
m.teach("the red block", frame, xy=(794, 432))        # pixels
m.teach("the red block", frame, box=(760, 400, 70, 64))  # x, y, w, h
```

Pass `rgb=True` if your frame is already RGB. Exactly one of `uv`, `xy`, `box`.

<Note>
  `teach` writes **both** halves of the appearance model — a colour example and a
  normalised-cross-correlation template in feature space — because the template
  alone does not create the prototype, and colour alone cannot separate two
  objects that share it. It also registers the bare noun phrase alongside the
  article form (`red block` as well as `the red block`), so a lookup cannot miss
  by an article.
</Note>

## Stepping

```python theme={null}
g = m.step(frame,
           joints=[...],        # the only honest state on a joint-commanded arm
           ee_pos=[x, y, z],    # optional
           gripper=0.3,         # scalar or (2,) — widened for you
           proposed=action)     # your policy's action, any width
```

<Note>
  **Action width is handled for you, and this one bites hard otherwise.** megan
  reads the commanded gripper from `proposed[:, 6]` — six pose degrees of freedom
  then the gripper. A narrower action vector (a 5-joint arm plus a gripper, say)
  raises `IndexError` from deep inside a risk evaluation, a long way from the
  cause. `step` pads into the expected layout and mirrors your last column into
  the gripper slot, so `commanded_grip` reads what you meant.
</Note>

## Diagnosing a quiet megan

A megan that produces no guidance is either **satisfied** or **blind**, and
they need opposite fixes.

```python theme={null}
m.blind()        # True -> the relation could not be measured at all
m.untaught()     # entities with no prototype: the usual cause
m.locate(frame)  # what the grounder currently sees; conf near 1.0 is good
m.explain(3)     # megan's own trace of the last three steps
m.summary()      # banked/total subgoals, steps, interventions
```

If `blind()` is True on every step, you are missing a prototype — check
`untaught()` before concluding megan has nothing to offer.

## The wake gate

megan can learn *when* intervening is worth it, from a run in which it did not
intervene. Build features from its risk reports, label the frames where help
was about to be needed, and fit:

```python theme={null}
from megantk.relational.gate import features_from

X, y = [], []
for frame in baseline_run:                  # megan stepped, guidance ignored
    m.step(frame, joints=q)
    r = m.last_report
    X.append(features_from(r, banked=r.banked, total=r.total,
                           acted=acted, steps=steps))
    y.append(1 if trouble_within(6) else 0)  # your task's definition

verdict = m.fit_gate(X, y)
verdict["fitted"]      # True once it beat a shuffled-label control
verdict["z"]           # by how much
```

<Warning>
  **A refusal is a finding, not an error.** `fit_gate` runs a permutation test
  against shuffled labels and refuses unless your features beat chance, leaving
  an untrained gate that licenses every wake. `fit_gate` returns that verdict —
  including `z`, `held_out_loss`, `shuffled_loss`, and how many of your features
  actually vary — so you can tell a fitted gate from an unfitted one.

  Two refusals look identical and mean opposite things:

  * **the features carry no signal** → change the features
  * **the baseline barely failed** → there was nothing to learn from; make the
    baseline run harder

  Check the positive-label rate to tell them apart. Measured on a real tracking
  task, the same feature set scored `z = 9.87` on a baseline with 142 failure
  frames and `z = 0.09` on one with 32 — the gate can only learn to predict
  failures that happened.
</Warning>

## Reference

### `Megan(instruction, *, grounder=None, experience=None, warn=True, **governor_kwargs)`

| Argument      | Type                        | Purpose                                                            |
| ------------- | --------------------------- | ------------------------------------------------------------------ |
| `instruction` | `str`                       | Parsed into the relation chain. Must be a recognised phrasing.     |
| `grounder`    | `PrototypeGrounder \| None` | Reuse a grounder already taught elsewhere. A fresh one by default. |
| `experience`  | `Experience \| None`        | Persisted remedy rankings and calibration across runs.             |
| `warn`        | `bool`                      | Warn when the instruction parses to nothing.                       |

| Property            | Returns                                    |
| ------------------- | ------------------------------------------ |
| `parsed`            | `bool` — the instruction yielded relations |
| `subgoals`          | `tuple[str, ...]` — the chain              |
| `entities`          | `dict[str, str]` — name → role             |
| `plan`              | megan's `Plan`                             |
| `last_report`       | the most recent `RiskReport`, or `None`    |
| `gate`              | the `WakeGate`, fitted or not              |
| `intervention_rate` | `float`                                    |

| Method                                          | Returns           |
| ----------------------------------------------- | ----------------- |
| `teach(entity, image, *, uv/xy/box, rgb=False)` | `bool`            |
| `untaught()`                                    | `tuple[str, ...]` |
| `locate(image, names=None, rgb=False)`          | `dict[str, dict]` |
| `percept(image, **state)`                       | `Percept`         |
| `step(image, **state)`                          | `Guidance`        |
| `blind()`                                       | `bool`            |
| `explain(n=1)`                                  | `str`             |
| `summary()`                                     | `dict`            |
| `reset(instruction=None)`                       | `None`            |
| `fit_gate(features, labels)`                    | `dict` verdict    |

### Errors

`MeganUnavailable` (a `RuntimeError`) when `megantk` is not importable, kept
distinct from a bare `ImportError` so you can tell "megan is not installed"
from a bug in your own imports. `relational_available()` checks without
raising.
