Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,19 @@
## Raspberry Pi Endpoint Provider Rules

- Raspberry Pi 5 / Linux ARM64 support is an Endpoint Provider only. Do not turn it into Core and do not let it own Thread, Message, Run, Scheduler, `system.heartbeat`, Memory, Delivery, Operation records, ToolRouter, or persistence.
- Raspberry Pi provider work lives under `endpoint_providers/raspberry_pi/`, with docs in `docs/endpoints/raspberry-pi.md`, `docs/endpoints/rpi-capabilities.md`, and `docs/endpoints/rpi-security.md`.
- Raspberry Pi provider work lives under `endpoint_providers/raspberry_pi/`, with docs in `docs/endpoints/raspberry-pi.md`, `docs/endpoints/rpi-capabilities.md`, `docs/endpoints/rpi-security.md`, `docs/endpoints/rpi-operations.md`, and real hardware acceptance records under `docs/endpoints/rpi-real-acceptance-*.md`.
- The Pi must actively connect to Core through `GET /endpoint/ws` using `meetyou.endpoint.ws.v4`; do not add an inbound Pi server or revive `/client/ws`.
- Pi executable tools must be advertised as EndpointCapability snapshots and executed only from Core-routed `tool.call.request` frames. Results must return through `tool.call.accepted` / `tool.call.progress` / `tool.call.result` / `tool.call.error`.
- `endpoint.heartbeat` from the Pi is connection keepalive only and must not trigger Scheduler-owned `system.heartbeat`.
- GPIO and shell capabilities must be deny-by-default: no GPIO outside `security.gpio_allowed_pins`, no arbitrary shell strings, no `shell=True`, and `rpi.shell.safe_exec` must not be advertised unless enabled with an allowlist.
- Raspberry Pi device abstractions (`rpi.device.*` / `rpi.button.read`) must remain endpoint-local wrappers over allowlisted GPIO. Device config must validate `device_id`, BCM pin allowlist, type/direction, relay confirmation defaults, and pulse/blink bounds without changing Core routing or endpoint protocol ownership.
- Raspberry Pi device capabilities must surface through existing endpoint inventory and tool-call paths: `list_endpoint_tool_targets`, `list_active_endpoints`, and `send_endpoint_message(delivery_kind="tool_call")`. Do not add Pi-specific Core inventory APIs or endpoint protocol extensions for device calls.
- Raspberry Pi device confirmation is currently capability-level, not per-device. A write-class `rpi.device.*` capability may advertise `requires_confirmation=true` when any configured output device requires confirmation; do not redesign Core approval for per-device confirmation unless explicitly scoped.
- Raspberry Pi GPIO arguments use BCM numbering, not physical header pin numbers.
- Raspberry Pi 5 GPIO must use gpiozero with `lgpio`; keep `MEETYOU_RPI_GPIO_PIN_FACTORY=lgpio` in the service environment and do not fall back to legacy `RPi.GPIO`/native backends.
- Raspberry Pi output device status reads must not open floating gpiozero inputs without `active_state`; Desk LED / Relay status on pins such as BCM 17/27 should use the lgpio backend and explicit active-state handling rather than surfacing gpiozero `active_state` errors to Core.
- The Pi systemd service must run as `meetyou-rpi`, include the `gpio` supplementary group, and use `/var/lib/meetyou-rpi` for `WorkingDirectory` / `TMPDIR`; `lgpio` creates `.lgd-*` runtime files and must not write into `/opt/meetyou/MeetYou`.
- Manual Pi diagnostics run as `meetyou-rpi` from `/var/lib/meetyou-rpi` must include `PYTHONPATH=/opt/meetyou/MeetYou`; the endpoint package imports the repository-local `endpoint_tool_sdk`, and systemd sets this environment variable for the service.
- GPIO diagnostics for the service user should run from `/var/lib/meetyou-rpi`, not from the repository checkout. `can not open gpiochip` means `/dev/gpiochip*` permission/group setup is wrong, not a ToolRouter problem.
- The Pi endpoint may emit operation progress/result/error, but final assistant replies must still be persisted by Core MessageService.

Expand Down Expand Up @@ -219,6 +224,8 @@
- Desktop Provider: `python main.py desktop-client` or `python -m desktop_client`
- Edge Provider: `python main.py edge-client` or `python -m edge_client`
- Research Adapter: `python -m research_adapter`
- Raspberry Pi endpoint health on Pi: `sudo -u meetyou-rpi env PYTHONPATH=/opt/meetyou/MeetYou TMPDIR=/var/lib/meetyou-rpi bash -lc 'cd /var/lib/meetyou-rpi && /opt/meetyou/MeetYou/.venv-rpi/bin/python -m meetyou_rpi_endpoint.health --config /etc/meetyou/rpi-endpoint.json --env-file /etc/meetyou/rpi-endpoint.env'`
- Raspberry Pi endpoint smoke: `bash scripts/rpi/smoke-test.sh /etc/meetyou/rpi-endpoint.json`
- Frontend development: run `npm install`, `npm run dev` under `meetyou-ui/`
- Frontend verification: run `npm run typecheck`, `npm run test` under `meetyou-ui/`
- Frontend build: run `npm run build` under `meetyou-ui/`
Expand Down
8 changes: 6 additions & 2 deletions core/tool_runtime/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,14 @@
"name": "list_endpoint_tool_targets",
"description": (
"List online Endpoint execution targets that can execute tools, optionally scoped to a workspace and tool key. "
"Examples include desktop file.read/shell.exec and Raspberry Pi rpi.device.list/status/set/pulse/blink/button.read. "
"When answering the user, prefer tool_target_lines, endpoint_ids, and executable_tools_by_endpoint before verbose endpoint records."
),
"parameters": {
"type": "object",
"properties": {
"workspace_id": {"type": "string", "description": "Optional workspace id.", "default": ""},
"tool_key": {"type": "string", "description": "Optional endpoint capability tool key, such as file.read or shell.exec.", "default": ""},
"tool_key": {"type": "string", "description": "Optional endpoint capability tool key, such as file.read, shell.exec, or rpi.device.list.", "default": ""},
"include_tools": {"type": "boolean", "description": "Include each endpoint's capability list.", "default": True},
"include_capability_details": {"type": "boolean", "description": "Include verbose per-capability metadata. Keep false for ordinary inventory answers.", "default": False},
"include_endpoint_details": {"type": "boolean", "description": "Include verbose endpoint records. Keep false so compact endpoint rows stay complete.", "default": False},
Expand Down Expand Up @@ -278,7 +279,10 @@
"normal current-session reply path. Do not use this to answer the originating user or to send "
"progress updates to the same endpoint; use emit_progress_notice for progress and the final assistant "
"answer for the actual reply. Use only when the user explicitly asks to notify or call a "
"specific target Endpoint."
"specific target Endpoint. For Raspberry Pi device calls, use delivery_kind=tool_call with "
"target_id set to the rpi endpoint_id, tool_key such as rpi.device.list or rpi.device.set, "
"arguments such as {\"device_id\":\"desk_led\",\"value\":true}, and confirmed=true only after "
"explicit confirmation when the capability requires it."
),
"parameters": {
"type": "object",
Expand Down
1 change: 1 addition & 0 deletions deploy/systemd/meetyou-rpi-endpoint.service
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Group=meetyou-rpi
SupplementaryGroups=gpio
WorkingDirectory=/var/lib/meetyou-rpi
EnvironmentFile=/etc/meetyou/rpi-endpoint.env
Environment=MEETYOU_RPI_GPIO_PIN_FACTORY=lgpio
Environment=PYTHONPATH=/opt/meetyou/MeetYou
Environment=TMPDIR=/var/lib/meetyou-rpi
ExecStart=/opt/meetyou/MeetYou/.venv-rpi/bin/python -m meetyou_rpi_endpoint.main --config /etc/meetyou/rpi-endpoint.json
Expand Down
9 changes: 7 additions & 2 deletions docs/endpoints/raspberry-pi.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Implementation decisions:
- Advertise a single execution endpoint, `rpi.<endpoint_id>.executor`; do not create Thread, Message, Run, Scheduler, Heartbeat, Delivery, or Operation state on the Pi.
- Map requested operation terms to current MeetYou frames: Core sends `tool.call.request` with `operation_id` and `call_id`; the Pi returns `tool.call.accepted`, `tool.call.progress`, `tool.call.result`, or `tool.call.error`.
- Keep `endpoint.heartbeat` as connection keepalive only. It must not trigger `system.heartbeat`, which remains Scheduler-owned by Core.
- Start with `rpi.echo`, `rpi.system.info`, `rpi.gpio.read`, `rpi.gpio.write`, and optional `rpi.shell.safe_exec`; camera, audio, display, and sensor streaming remain future work.
- Start with `rpi.echo`, `rpi.system.info`, low-level `rpi.gpio.read` / `rpi.gpio.write`, named device capabilities, and optional `rpi.shell.safe_exec`; camera, audio, display, and sensor streaming remain future work.
- Keep GPIO and shell policies local, explicit, and deny-by-default. GPIO writes require an allowlisted pin. Safe shell is not advertised unless enabled and an allowlist is configured.

## Architecture Summary
Expand Down Expand Up @@ -117,6 +117,7 @@ Key fields:
- `keepalive`: endpoint connection heartbeat interval and timeout policy.
- `operation`: default and max local operation timeout.
- `security`: sandbox, safe-shell allowlist, GPIO allowlist, and default GPIO write duration.
- `devices`: named device abstraction records. Each record uses a stable `device_id`, BCM `pin`, `type`, `direction`, `active_high`, and optional output/input safety fields.

Environment overrides:

Expand All @@ -142,14 +143,18 @@ sudo systemctl status meetyou-rpi-endpoint
journalctl -u meetyou-rpi-endpoint -f
```

A healthy startup shows `hello acknowledged` followed by `ready: {'registered_capability_count': 4}`. That confirms Core accepted the endpoint and capability snapshot.
A healthy startup shows `hello acknowledged` followed by `ready: {'registered_capability_count': 10}` when safe shell is disabled and the default device capability set is advertised. That confirms Core accepted the endpoint and capability snapshot.

Smoke test before starting systemd:

```bash
bash scripts/rpi/smoke-test.sh
```

The smoke wrapper first runs the production health check, which verifies config presence, token env presence without printing the token, sandbox writability, `lgpio`, `gpio` group membership, `/dev/gpiochip*` permissions, and systemd status with explicit `PASS` / `WARN` / `FAIL` lines. See `docs/endpoints/rpi-operations.md` for the operations runbook and `docs/endpoints/rpi-real-acceptance-2026-05-13.md` for the real Raspberry Pi 5 acceptance record.

When running the health module manually as `meetyou-rpi` from `/var/lib/meetyou-rpi`, include `PYTHONPATH=/opt/meetyou/MeetYou`. The systemd unit already sets it, but manual commands need it so `meetyou_rpi_endpoint` can import the repository-local `endpoint_tool_sdk`.

Raspberry Pi 5 GPIO must use the `lgpio` pin factory. `/etc/meetyou/rpi-endpoint.env` should include:

```bash
Expand Down
106 changes: 105 additions & 1 deletion docs/endpoints/rpi-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Raspberry Pi capabilities are endpoint-local executable tools advertised through
- Risk: `read`
- Confirmation: no
- Input: empty object
- Output: hostname, platform, Python version, uptime when available, memory summary when available, disk summary, and CPU temperature when available.
- Output: hostname, platform, Python version, uptime when available, memory summary when available, disk summary, CPU temperature when available, GPIO backend info, endpoint version, and git commit when available.
- Notes: CPU temperature and `/proc` data are optional. The capability must not fail on non-Pi development machines just because those files are absent.

### `rpi.gpio.read`
Expand All @@ -42,6 +42,110 @@ Raspberry Pi capabilities are endpoint-local executable tools advertised through
- Raspberry Pi 5: requires `lgpio`; install `python3-lgpio` or pip package `lgpio` and keep the service env set to `MEETYOU_RPI_GPIO_PIN_FACTORY=lgpio`.
- Device access: `can not open gpiochip` means the service user lacks `/dev/gpiochip*` permission. The deployment path adds `meetyou-rpi` to the `gpio` group and the systemd unit uses `SupplementaryGroups=gpio`.

### Device abstraction capabilities

These wrap allowlisted GPIO pins with named, typed device records from `devices` in the Raspberry Pi endpoint config. They do not replace `rpi.gpio.read` / `rpi.gpio.write`; the low-level primitives remain available for diagnostics and controlled one-off operations.

Device config fields:

- `device_id`: stable identifier used in operation arguments, for example `desk_led`.
- `type`: `led`, `relay`, `output`, `button`, or `input`.
- `name`: human-readable display name.
- `pin`: BCM GPIO number; it must also appear in `security.gpio_allowed_pins`.
- `direction`: `out` for `led` / `relay` / `output`; `in` for `button` / `input`.
- `active_high`: logical `true` maps to physical high when true, and to physical low when false.
- `max_on_ms`: optional output safety limit used by pulse and blink.
- `requires_confirmation`: optional output confirmation override. Relays default to true when omitted.
- `pull`: optional input pull mode, one of `up`, `down`, or `none`.

Example:

```json
{
"security": {
"gpio_allowed_pins": [17, 27, 22]
},
"devices": [
{
"device_id": "desk_led",
"type": "led",
"name": "Desk LED",
"pin": 17,
"direction": "out",
"active_high": true,
"max_on_ms": 5000,
"requires_confirmation": false
},
{
"device_id": "relay_1",
"type": "relay",
"name": "Relay 1",
"pin": 27,
"direction": "out",
"active_high": true,
"max_on_ms": 3000
},
{
"device_id": "button_1",
"type": "button",
"name": "Button 1",
"pin": 22,
"direction": "in",
"active_high": false,
"pull": "up"
}
]
}
```

### `rpi.device.list`

- Risk: `read`
- Confirmation: no
- Input: empty object
- Output: configured device records with `device_id`, type, name, BCM pin, direction, `active_high`, and non-secret safety metadata.
- Policy: never returns tokens or endpoint secrets.

### `rpi.device.status`

- Risk: `read`
- Confirmation: no
- Input: `{ "device_id": string }`
- Output: device metadata, logical `value`, and raw GPIO `raw_value`.
- Policy: rejects unknown devices and rejects any config drift that places the device pin outside `security.gpio_allowed_pins`.

### `rpi.device.set`

- Risk: `local_write`
- Confirmation: yes when any configured output device requires confirmation; otherwise no.
- Input: `{ "device_id": string, "value": boolean|0|1|"on"|"off" }`
- Output: device metadata, logical value, raw GPIO value, and backend name.
- Policy: only `direction=out` devices can be set. Input devices fail with `device_permission_denied`.

### `rpi.device.pulse`

- Risk: `local_write`
- Confirmation: yes when any configured output device requires confirmation; otherwise no.
- Input: `{ "device_id": string, "duration_ms": integer }`
- Output: device metadata, duration, backend, and `reset_performed=true`.
- Policy: only output devices are allowed. `duration_ms` must be positive and must not exceed the device `max_on_ms`, or `5000` ms when no per-device limit is configured.

### `rpi.device.blink`

- Risk: `local_write`
- Confirmation: yes when any configured output device requires confirmation; otherwise no.
- Input: `{ "device_id": string, "count": integer, "interval_ms": integer }`
- Output: device metadata, count, interval, maximum total duration, backend, and final logical value false.
- Policy: only output devices are allowed. Count is capped at 20, interval is capped at 10000 ms and by output `max_on_ms`, and total duration is capped at 60000 ms.

### `rpi.button.read`

- Risk: `read`
- Confirmation: no
- Input: `{ "device_id": string }`
- Output: same shape as `rpi.device.status`.
- Policy: only configured `type=button`, `direction=in` devices are accepted.

## Disabled By Default

### `rpi.shell.safe_exec`
Expand Down
Loading
Loading