From d2e1bf8dfd55bb3f5a8a039578d27af7e14df06f Mon Sep 17 00:00:00 2001 From: wakonig_k Date: Tue, 1 Sep 2026 09:14:05 +0200 Subject: [PATCH] docs(AGENTS): update repository guidelines for clarity and structure --- AGENTS.md | 379 ++++++++++++++++++++++++++++++++---------------------- CLAUDE.md | 36 +----- 2 files changed, 229 insertions(+), 186 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5f49f11..4598dfa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,207 +1,282 @@ # Repository Guidelines — `ophyd_devices` -`ophyd_devices` is the **hardware abstraction layer** for -[BEC (Beamline Experiment Control)](https://github.com/bec-project/bec). It extends -[ophyd](https://github.com/bluesky/ophyd) with device support for hardware that the standard EPICS -implementation does not cover — motion controllers, detectors, shutters, undulators, monochromators — -plus a full simulation framework so BEC can run end-to-end with no hardware attached. +`ophyd_devices` provides reusable ophyd hardware support and simulation devices for +[BEC](https://github.com/bec-project/bec). Prefer focused changes, follow existing local patterns, +and verify the smallest relevant test scope. -This file is a quick-reference for AI coding agents (and new contributors). User-facing documentation -lives at ; general ophyd concepts are documented at -. +This file is an agent-oriented operating manual. User-facing documentation lives at + and is authored in the separate `bec_docs` repository. +Treat `pyproject.toml` as the source of truth for dependencies, scripts, and tool configuration. -## Project Structure & Module Organization +## Core Rules -`ophyd_devices/` is the importable package: +- Use `PSIDeviceBase` when a device needs BEC lifecycle or business logic. A device that merely + exposes signals can use plain `ophyd.Device`, regardless of the communication backend. +- Separate reusable device control from beamline-specific business logic in `on_*` hooks. +- Prefer repository statuses, signals, and helpers when a BEC-aware counterpart exists. +- Return promptly from device-server calls. Represent unfinished work with a status. +- Implement safe interruption through `on_stop()` and register cancellable statuses. +- Use `bec_signals.py` for BEC live data, progress, and file events. +- Check the relevant device protocols when changing interfaces. +- Add an example configuration for each new reusable device family. +- Do not edit `ophyd_devices/devices/device_list.md`; CI generates it. +- Follow existing local patterns before introducing a new abstraction. +- Keep diffs focused and preserve unrelated local changes. +- Add regression tests for bug fixes. +- Do not commit, push, open a PR, or post a review unless explicitly requested. -| Path | What goes there | -| --- | --- | -| `ophyd_devices/interfaces/base_classes/` | The classes you should inherit from: `PSIDeviceBase`, `PSIPositionerBase`, `PSIPseudoDeviceBase`, `PSIPseudoMotorBase`. Start here before writing a device. | -| `ophyd_devices/interfaces/protocols/` | `typing.Protocol` definitions (`BECDeviceProtocol`, `BECPositionerProtocol`, `BECFlyerProtocol`, …) describing what BEC expects from a device. Useful as a checklist and in `isinstance` tests. | -| `ophyd_devices/interfaces/device_config_templates/` | Templates for generating device configuration entries. | -| `ophyd_devices/devices/` | Concrete device implementations (`psi_motor.py`, `undulator.py`, `optics_shutter.py`, `dxp.py`, `areadetector/`, `panda_box/`, …), plus the generated `device_list.md`. | -| `ophyd_devices/sim/` | The simulation framework: `SimPositioner`, `SimCamera`, `SimMonitor`, `SimWaveform`, `SimFlyer`, and the `sim_data.py` data generators behind them. | -| `ophyd_devices/utils/` | Shared helpers: `bec_signals.py`, `bec_scaninfo_mixin.py`, `controller.py`, `socket.py`, `psi_device_base_utils.py` (`FileHandler`, `TaskHandler`), `static_device_test.py`. | -| `ophyd_devices/configs/` | Example device configuration YAML files, including the simulation config used for local BEC runs. | -| `ophyd_devices/npoint/`, `rt_lamni/`, `sls_devices/`, `smaract/` | Vendor- and facility-specific integrations. | -| `tests/` | The test suite (flat; one `test_.py` per area). | +## First Read -`ophyd_devices/devices/device_list.md` is **generated by CI** on pushes to `main` — do not edit it by hand. +Read the affected implementation and its tests. For unfamiliar areas, start here: -## Local Environment Overlay +- `ophyd_devices/interfaces/base_classes/psi_device_base.py` — device lifecycle and BEC integration +- `ophyd_devices/interfaces/base_classes/psi_positioner_base.py` — motion +- `ophyd_devices/interfaces/base_classes/psi_pseudo_device_base.py` — pseudo devices +- `ophyd_devices/interfaces/base_classes/psi_pseudo_motor_base.py` — pseudo motors +- `ophyd_devices/interfaces/protocols/bec_protocols.py` — expected device contracts +- `ophyd_devices/utils/psi_device_base_utils.py` — statuses, tasks, and file helpers +- `ophyd_devices/utils/bec_signals.py` — live data publishing +- `tests/conftest.py` and `ophyd_devices/tests/utils.py` — reusable test helpers +- `README.md` — project overview -If a file named **`AGENTS_PERSONAL.md`** exists next to this one, read it and treat it as an extension -of this file. It carries machine-specific setup — interpreter and environment manager, local paths, -private workflow conventions — and **its instructions take precedence over the generic -"Development Environment" section below**. Everything else in this file still applies. +## Repo Layout -That file is intentionally untracked and personal to one developer's machine. Do not commit it, do not -reference it from committed files, and do not assume it exists — if it is absent, follow this file as -written. +- `ophyd_devices/interfaces/` — base classes, protocols, and device configuration templates +- `ophyd_devices/devices/` — concrete devices and the generated device list +- `ophyd_devices/sim/` — simulation devices and data generators +- `ophyd_devices/utils/` — shared signals, statuses, controllers, and other helpers +- `ophyd_devices/configs/` — example device configurations +- `tests/` — unit tests -## Development Environment +Related but separate repositories: -Requires **Python 3.11+** (CI tests 3.11, 3.12, 3.13). +- `bec` — core messaging, scans, services, and the client +- `bec_widgets` — GUI widgets +- `bec_docs` — published documentation +- beamline plugin repositories — beamline-specific devices, scans, and widgets -```bash -python -m venv .venv -source .venv/bin/activate # macOS/Linux only; see "Platform Notes" -python -m pip install --upgrade pip -python -m pip install -e '.[dev]' -``` +## Local Overlay -The `dev` extra pulls in `bec-server`, which is what the device-server-facing tests exercise. Verify the -environment resolves to this checkout: +If `AGENTS_PERSONAL.md` exists beside this file, read it as an extension of these instructions. +Its machine-specific environment and workflow guidance takes precedence over the corresponding +sections here. Keep it local and untracked; do not copy its contents into committed files. -```bash -python -c "import ophyd_devices; print(ophyd_devices.__file__)" -``` +## Common Task Routing -No EPICS IOC or hardware is needed for development: unit tests mock connections, and the `sim/` devices -provide a full working beamline in software. +If you change: -## Writing a Device +- `ophyd_devices/interfaces/base_classes/*`: inspect relevant protocols and affected concrete and + simulation devices; test staging, subscriptions, movement, and stop behavior. +- `ophyd_devices/utils/psi_device_base_utils.py`: check timeout handling and composition across + status subclasses. +- `ophyd_devices/utils/bec_signals.py`: check device-server consumers and report compatibility + risks for `bec` and `bec_widgets`. +- `ophyd_devices/sim/*`: check the affected device and tests consuming its scan data. +- `ophyd_devices/devices/*` or vendor integrations: add targeted device tests; include an example + configuration and validation notes for a new reusable device family. +- `ophyd_devices/configs/*`: run `ophyd_test --config ` and inspect its report. +- Documentation or templates: verify referenced paths, commands, examples, and metadata. + A broad unit test run is unnecessary when executable behavior is unchanged. -**Inherit from a `PSI*` base class, not from `ophyd.Device` directly.** `PSIDeviceBase` wires up the -subscription types BEC's device manager expects (`readback`, `value`, `done_moving`, `motor_is_moving`, -`progress`, `file_event`, `device_monitor_1d`, `device_monitor_2d`), gives you `scan_info` and -`device_manager`, and provides the `FileHandler` / `TaskHandler` utilities. A bare `ophyd.Device` will -appear to work locally and then misbehave inside a running BEC deployment. +Reusable hardware support belongs here. Devices specific to one beamline belong in its plugin +repository. Route core messaging, scans, and service changes to `bec`, GUI behavior to +`bec_widgets`, and published documentation to `bec_docs`. -```python -from ophyd import Component as Cpt, EpicsSignal, EpicsSignalRO +## Writing A Device -from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase +### Control and business logic +The device's base control class defines how to communicate with the hardware: signal +definitions, commands, protocol handling, and device state. Keep this layer reusable across +beamlines and implement it in `ophyd_devices`. Plain ophyd control classes may be composed into +or combined with a class that uses `PSIDeviceBase` when business logic is needed. -class MyDetector(PSIDeviceBase): - """One-line description; this text reaches the generated device list.""" +A device that only exposes a collection of signals needs no business-logic layer; plain +`ophyd.Device` is enough. For example, `SLSOperatorMessages` in +`ophyd_devices/devices/sls_devices.py` groups operator messages and their dates using dynamic +components without scan-specific behavior. This applies equally to EPICS and other communication +backends; the need for business logic determines the base class, not the transport. - acquire = Cpt(EpicsSignal, "ACQ", kind="omitted") - readback = Cpt(EpicsSignalRO, "VAL", kind="hinted") +The `on_*` hooks typically describe business logic: how a beamline uses that control interface +during a scan. For example, the control class exposes acquisition and trigger-mode commands; +a beamline's `on_stage()` chooses the trigger mode and acquisition settings for its experiment, +and `on_trigger()` starts acquisition through the control interface. - def on_stage(self) -> None: - ... # prepare for a scan +Implement bespoke hook behavior in a subclass in the beamline plugin repository. Keep shared +lifecycle behavior generic, and make hooks call reusable control methods rather than duplicating +PV definitions or communication code. This lets beamlines share the same hardware support while +choosing their own acquisition and scan behavior. - def on_complete(self) -> None: - ... # wait for acquisition to finish +### Base classes and hooks - def on_unstage(self) -> None: - ... # release resources -``` - -Rules of thumb: - -- **Where `ophyd_devices` provides a counterpart to an `ophyd` class, always import the - `ophyd_devices` one.** Several ophyd classes are subclassed here to add BEC behaviour, and the plain - ophyd version silently loses it. The status classes in `ophyd_devices/utils/psi_device_base_utils.py` - — `StatusBase`, `Status`, `DeviceStatus`, `MoveStatus`, `SubscriptionStatus`, `AndStatus` — add - timeout diagnostics that report which device and which call is stuck, and the `&` operator for - composing statuses. The same module adds BEC-only statuses with no ophyd equivalent - (`CompareStatus`, `ExceptionStatus`, `TransitionStatus`, `TaskStatus`), and signals that publish to - BEC live in `ophyd_devices/utils/bec_signals.py`. +- When lifecycle or business logic is needed, use `PSIDeviceBase` to integrate the control class + with BEC's lifecycle, scan information, statuses, and subscriptions. Do not add it solely to + expose a collection of signals. +- Use the lifecycle hooks provided by the base class rather than replacing its wrappers: + `on_init`, `on_connected`, `on_stage`, `on_pre_scan`, `on_trigger`, `on_complete`, + `on_kickoff`, `on_unstage`, `on_stop`, and `on_destroy`. +- When implementing a device inheriting from `PSIDeviceBase`, copy the entire + "Beamline Specific Implementations" section from + `ophyd_devices/interfaces/base_classes/psi_device_base.py`, including its separator, all ten + hooks, signatures, and docstrings. Keep the hooks together in that order, including unused + hooks, so readers can quickly identify the device's business logic. +- Put helper methods outside that section under a separate separator, for example: ```python - from ophyd_devices.utils.psi_device_base_utils import DeviceStatus, MoveStatus # yes - from ophyd.status import DeviceStatus, MoveStatus # no + ######################################## + # Beamline Specific Implementations # + ######################################## + + # All ten on_* hooks belong here. + + ######################################## + # Helper Methods # + ######################################## ``` - The same applies to anything re-exported from `ophyd_devices/__init__.py`. Importing straight from - `ophyd` stays correct only for what has no counterpart here — `Component`, `EpicsSignal`, `Kind`, - `PositionerBase`, and friends. -- **Never block.** Long-running work goes through `TaskHandler`, and completion is reported with a - `DeviceStatus`. A device that blocks in `stage()` or `trigger()` stalls the whole device server. -- **Set `kind` deliberately.** `hinted` signals are recorded by default; `omitted` and `config` are not. - A wrong `kind` either loses data or floods every scan file. -- **Always implement `stop()`** so the device can be interrupted mid-scan and leaves the hardware safe. -- **Emit BEC data through `ophyd_devices/utils/bec_signals.py`** rather than inventing a message shape. -- **Check your device against the protocols** in `interfaces/protocols/bec_protocols.py` — they are the - contract BEC relies on. -- **Add an example configuration** under `ophyd_devices/configs/` when adding a new device family. +- When replacing a hook in a subclass, preserve any required parent behavior with `super()`; + copied hook stubs must not silently disable an inherited implementation. +- Constructors and `on_init()` must not communicate with devices. Use `on_init()` only for local + initialization; do not read hardware state or send commands there. +- `on_connected()` is the first hook allowed to communicate with devices and send instructions, + such as setting hardware defaults. The device server calls it once an enabled device has + connected; plain instantiation, unit tests, and `ophyd_test` do not, so call it explicitly in + tests that rely on it. It is not a per-scan hook. Use the scan lifecycle hooks for + scan-specific instructions and access scan parameters through `self.scan_info.msg`. +- Check the relevant protocols when adding or changing a device interface. Read the base-class + implementation for hook return semantics; the protocol signatures alone do not describe them. -### Validating a device configuration +### Completion and interruption -`ophyd_test` statically analyses a device configuration YAML, and can optionally connect to the hardware: +- Acquisition, staging, completion, and movement must return promptly to the device server. + Represent unfinished work with a status; do not wait, sleep, or poll on the calling thread. +- Import statuses from `ophyd_devices.utils.psi_device_base_utils`, including `DeviceStatus`, + `MoveStatus`, and `StatusBase`. These provide BEC timeout diagnostics and status composition. + Prefer repository helpers whenever a BEC-aware counterpart exists. +- Hooks such as `on_stage()` and `on_complete()` may return a status or `None`. Return `None` + only when there is no outstanding work. In particular, `complete()` converts `None` into + an already-finished status; return a pending status while acquisition or file writing continues. +- Register statuses that must fail on interruption with `self.cancel_on_stop(status)`. + Implement hardware interruption in `on_stop()` and make repeated calls safe. Cancelling a + status does not by itself stop hardware or a background task. +- Preserve base-class stop and destroy behavior. Release subscriptions, threads, sockets, and + other owned resources in `on_destroy()`; worker code must be able to exit on interruption. + +### Signals and configuration + +- Set signal `kind` deliberately. `normal` and `hinted` signals appear in `read()`; `hinted` + also selects default BEC scan readouts. `config` signals appear in `read_configuration()`; + `omitted` signals appear in neither. Treat changes to kinds and names as data-interface changes. +- Use the signals in `ophyd_devices/utils/bec_signals.py` for BEC live data, progress, and file + events. Reuse their message formats instead of publishing custom Redis messages. +- Add an example configuration under `ophyd_devices/configs/` for a new reusable device family. +- Give each device class a docstring with a useful first-line description. CI uses it to generate + `ophyd_devices/devices/device_list.md`; do not edit that generated file by hand. + +### USER_ACCESS + +- Prefer methods over properties for functionality exposed through `USER_ACCESS`. +- Give exposed methods verb-based names that describe the operation, such as `set_velocity()` + or `get_velocity()`, rather than noun-only names such as `velocity()`. + +## Validation + + +Add regression tests for bug fixes. New device tests must cover instantiation, the relevant +protocol, and safe `stop()` behavior. For asynchronous changes, also exercise completion, +failures, and interruption while work is pending. + +Use mocked EPICS and sockets for unit tests. Reuse `get_mock_scan_info` from +`ophyd_devices/tests/utils.py` and existing fixtures; use simulation devices when a working device +is needed. Keep tests independent of execution order. + +Run the smallest relevant test target first, with `--random-order` when available: ```bash -ophyd_test --config ./ophyd_devices/configs/ophyd_devices_simulation.yaml -ophyd_test --config /path/to/beamline_config.yaml --connect --timeout-per-device 30 +python -m pytest --random-order tests/test_psi_device_base.py ``` -Reports are written to `./device_test_reports` by default. Run this before proposing a configuration -change for a real beamline. - -## Testing +For a full unit test or coverage run: ```bash -python -m pytest --random-order ./tests -``` - -`--random-order` matches CI and is how order-dependent test pollution gets caught. - -Coverage, as CI measures it: - -```bash -coverage run --source=./ophyd_devices --omit=*/ophyd_devices/tests/* -m pytest --random-order ./tests +python -m pytest --random-order tests +coverage run --source=./ophyd_devices --omit='*/ophyd_devices/tests/*' \ + -m pytest --random-order tests coverage report ``` -**Conventions:** +`ophyd_test` writes reports to `./device_test_reports` by default. Use `--connect` only when the +user explicitly requests hardware validation and the target is reachable. Report whether device +validation used mocks, simulation, or real hardware; include the model and firmware when known. -- Name files `test_.py` and tests after behaviour — `test_positioner_reports_done_after_move()`, - not `test_positioner_3()`. -- Mock EPICS and sockets. Use `get_mock_scan_info` from `ophyd_devices/tests/utils.py` and the fixtures - in `tests/conftest.py` rather than constructing scan metadata by hand. -- Prefer building on the `sim/` devices when you need a working device in a test. -- Every new device class needs at least: it instantiates, it satisfies the relevant protocol, and its - `stop()` is safe to call. +## Running BEC Locally -## Coding Style & Naming Conventions +Full service validation needs Redis, usually at `localhost:6379`, and an environment with BEC +services installed. Unit tests normally use mocks and need no hardware. -- Python 3.11+, 4-space indentation, **100-character** line limit. -- **Black** and **isort** are the source of truth (settings in `pyproject.toml`). CI fails on any diff: +When service management is part of the requested validation, start services and open the client: - ```bash - black --line-length=100 --skip-magic-trailing-comma . - isort --line-length=100 --profile=black --multi-line=3 --trailing-comma . - ``` +```bash +bec-server start +bec +``` -- **Pylint** runs in CI and reports a score; do not introduce new warnings. Beamline-idiomatic names - (`scanID`, `RID`, `pointID`, `*_1D`, `*_2D`) are explicitly allowed via `[tool.pylint.basic]`. -- `snake_case` for modules, functions, and test files; `PascalCase` for device classes. Device class - names should read as the hardware they represent (`SimPositioner`, `PSIMotor`, `DelayGenerator645`). +Run the client in another shell. Restart the device server after changing code it has already +loaded; otherwise a running session may use stale code. + +## Style And Change Hygiene + +- Use Python 3.11-compatible syntax, four-space indentation, and a 100-character line limit. - Use f-strings and `pathlib`. -- **Docstrings are not optional on device classes** — the first line is picked up by the generated - device list and is what beamline scientists read when choosing a device. +- Type-annotate new public functions and methods; document public APIs. +- Follow existing naming and docstring conventions. +- Run Black and isort on changed Python files using `pyproject.toml` configuration. +- Avoid formatting unrelated files and introducing new Pylint warnings. + +## Development Environment + +Use an existing suitable environment when available. To create one, use Python 3.11 or newer: + +```bash +python -m venv .venv +source .venv/bin/activate +python -m pip install -e '.[dev]' +python -c "import ophyd_devices; print(ophyd_devices.__file__)" +``` + +Verify imports resolve to the checkout under test. Separate worktrees need separate editable +installations; do not repoint another task's environment. The `dev` extra includes `bec-server`. ## Platform Notes -Code must run on **macOS and Linux**. Windows is not supported or tested. +Keep code portable across macOS and Linux. Windows is unsupported and untested. -## Related Repositories +## Commit And PR Notes -- [`bec`](https://github.com/bec-project/bec) — core library and services; `bec_lib` is a direct - dependency, and the device server here is driven by `bec_server`. -- [`bec_widgets`](https://github.com/bec-project/bec_widgets) — GUI toolkit that displays these devices. -- Beamline plugin repositories — beamline-specific devices that do not belong in this shared repository. +Branch from `main` for new work. Continue an existing PR on its current branch. -A device used at exactly one beamline belongs in that beamline's plugin repository. This repository is -for hardware support that is reusable across beamlines and facilities. -## Commit & Pull Request Guidelines +The manual PR template lives at `.github/PULL_REQUEST_TEMPLATE/pull_request_template.md`. +When writing a description: -- **Do not commit or push unless explicitly asked to.** Leave the working tree for the human to review. -- **Never open, update, or merge a pull request.** Submitting the change is the human contributor's - step. An agent's work ends at a reviewed working tree — or at a local commit on a branch, when a - commit was explicitly requested. -- Branch from `main` with a descriptive name such as `feat/panda-position-capture` or - `fix/undulator-timeout`. -- **Conventional Commits are mandatory** — `(): `, e.g. - `fix(psi_motor): report done_moving after limit hit`. Allowed types: `build`, `chore`, `ci`, `docs`, - `feat`, `fix`, `perf`, `refactor`, `style`, `test`. `feat` triggers a minor release, `fix` and `perf` a - patch release; breaking changes need `!` or a `BREAKING CHANGE:` footer. -- Commit messages are parsed by python-semantic-release and become the published `CHANGELOG.md`. Keep - them to a single clean subject line. -- The pull request itself needs a clear description, linked issues, and test evidence, and for a new - device it must state which hardware the device was tested against — or say explicitly that it has - only been tested in simulation. Put that in your summary so whoever opens the PR can carry it over. +- Lead with the concrete problem and resulting behavior. For a bug fix, explain the trigger and + before/after result. Describe the final change for a reviewer who has not followed the work. +- Keep detail proportional to the change. Replace prompts and remove unused sections; avoid a + file-by-file recap. Put lengthy examples in a `
` block and label before/after output. +- Use `Closes #123` for resolved issues and `Related to #123` for partial work. Link companion PRs + and `bec_docs` updates, including any required merge or deployment order. +- Give exact test commands or manual steps and expected outcomes. Separate instructions for + reviewers from checks already performed; report results and material limitations honestly. +- Explain compatibility changes, affected consumers, defaults, migrations, and remaining limits. + Include hardware/simulation validation and configuration-check results where applicable. +- State whether documentation was updated or why it is unnecessary. Add design tradeoffs and + follow-ups only when they help assess the change. Update the description when scope changes. + +Use Conventional Commit titles: `(): `. Allowed types are `build`, `chore`, +`ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `style`, and `test`. Mark breaking changes with +`!` or a `BREAKING CHANGE:` footer. + +For reviews, use `.github/pull_request_review_template.md`. Separate findings introduced or +exposed by the change from inherited issues and optional suggestions. Identify the reviewed +revision, give concrete evidence, and state validation limits. Return the review in chat unless +posting to GitHub is explicitly requested. diff --git a/CLAUDE.md b/CLAUDE.md index 44fde31..0ed2ee3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,37 +1,5 @@ -# CLAUDE.md — `ophyd_devices` +# `ophyd_devices` @AGENTS.md -The guidelines above are imported from [`AGENTS.md`](AGENTS.md) (single source of -truth). The points that matter most in day-to-day work: - -- **Check for `AGENTS_PERSONAL.md` first.** If it exists, it extends `AGENTS.md` with - machine-specific environment setup and takes precedence over the generic venv/pip instructions there. - It is untracked and personal — never commit it, and never assume it exists. -- **Inherit from `PSIDeviceBase` / `PSIPositionerBase` / `PSIPseudoDeviceBase`** - (`ophyd_devices/interfaces/base_classes/`), never from `ophyd.Device` directly — the base classes wire - up the subscriptions, `scan_info`, and task/file handling that BEC's device server expects. -- **Import the `ophyd_devices` counterpart, never the plain `ophyd` one, wherever one exists.** The - status classes in `ophyd_devices/utils/psi_device_base_utils.py` (`StatusBase`, `Status`, - `DeviceStatus`, `MoveStatus`, `SubscriptionStatus`, `AndStatus`) subclass ophyd's to add timeout - diagnostics and `&` composition, and the module adds BEC-only `CompareStatus`, `ExceptionStatus`, - `TransitionStatus`, `TaskStatus`; BEC-publishing signals live in `ophyd_devices/utils/bec_signals.py`. - Importing from `ophyd` directly silently drops that behaviour. Plain `ophyd` imports are correct only - where there is no counterpart (`Component`, `EpicsSignal`, `Kind`, …). -- **Never block the device server.** Long work goes through `TaskHandler` and reports completion with a - `DeviceStatus`. Always implement a safe `stop()`. -- **Set `kind` deliberately** (`hinted` / `config` / `omitted`) — it decides what lands in the scan file. - Emit BEC data through `ophyd_devices/utils/bec_signals.py`, and check the device against the protocols - in `interfaces/protocols/bec_protocols.py`. -- **Docstring every device class** — the first line feeds the generated - `ophyd_devices/devices/device_list.md`, which is CI-generated and must not be hand-edited. -- **Tests**: `python -m pytest --random-order ./tests`. Mock EPICS and sockets; build on the `sim/` - devices; use `get_mock_scan_info` and the `tests/conftest.py` fixtures. -- **Validate configs** with `ophyd_test --config ` (add `--connect` for real hardware). -- **Format before finishing**: `black --line-length=100 --skip-magic-trailing-comma .` and - `isort --line-length=100 --profile=black --multi-line=3 --trailing-comma .`. -- **A device used at only one beamline belongs in that beamline's plugin repo**, not here. -- **Do not commit or push unless explicitly asked, and never open a pull request.** If you do commit, - write a single Conventional Commits line — it is parsed into the published changelog. Opening the PR - is the human's step; leave them the summary and test output they need for it, including whether a new - device was tested against real hardware or only in simulation. +Follow the shared repository instructions in `AGENTS.md`, including its local-overlay guidance.