App integrations

# Device Access Plugin

Selected iPhone data and actions, requested through Hermes.

## [Set up device sharing](https://cadu.bot/docs/device-access#setup)

Device Access connects the `cadu-device` plugin to Apple APIs on a paired Cadu device. The audited plugin is version 1.2.3 and reports protocol version 2. The server queues work; the iPhone executes supported operations using EventKit, HealthKit, Contacts, and Core Location.

1.  Open Settings → Device Access, install or update the plugin, and complete any requested dashboard and gateway restarts.
2.  Select the Hermes agent whose access you are configuring. Sharing choices are stored per saved installation and profile on this device.
3.  Choose calendars, individual Health categories, or contact-detail categories. Enable the required read or write switches for Reminders, calendar creation, or location.
4.  Grant the corresponding iOS permissions with Cadu open. Keep Cadu connected while the choices synchronize to Hermes.

Empty calendar, Health, and contact selections grant no access to those categories. App-level iOS permission and per-agent sharing are separate checks. Calendar setup requests full EventKit access, but agent event operations filter to your selected calendars. Device Access can process foreground requests without push; optional notification setup provides best-effort background wake signals.

## [Available agent tools](https://cadu.bot/docs/device-access#tools)

The plugin registers ten tools in the `cadu_device` toolset. Their acting profile comes from Hermes runtime context, not a model-supplied profile parameter.

**`cadu_devices`**

Lists devices, current server-synced grants, selected calendar metadata, Health categories, contact fields, and last-seen timestamps for this profile.

**`cadu_request_result`**

Retrieves a previous request in the current profile. Can wait up to 20 seconds; its default wait is zero.

**`cadu_calendar_events`**

Reads events in selected calendars for an explicit date range.

**`cadu_calendar_create`**

Creates an event in a selected writable calendar. Requires calendar write access and an idempotency key.

**`cadu_reminders_list`**

Reads incomplete reminders from the lists available to the app.

**`cadu_reminder_create`**

Creates a reminder in the device's writable default list, optionally with a due date and alarm. Requires reminder write access and an idempotency key.

**`cadu_reminder_complete`**

Completes a reminder by its EventKit identifier. Requires reminder write access and an idempotency key.

**`cadu_health_summary`**

Reads selected steps, sleep, heart-rate, or workout data for a date range.

**`cadu_location`**

Requests a location fix with coordinates, accuracy, and timestamp while Cadu is active.

**`cadu_contacts_search`**

Searches by name, email address, or phone number and returns bounded matches with shared detail categories.

Operation tools accept an optional `device_id`. If multiple devices have the required grant, the agent must choose one from `cadu_devices`. These tools expose no event deletion, contact editing, Health writes, general file access, or arbitrary code execution on the phone.

## [Permission checks and revocation](https://cadu.bot/docs/device-access#permissions)

The server checks the profile's registered device grants and selected scope before queuing work. The device separately checks request profile, device identifier, expiry, known operation, and its locally saved effective permissions. Native providers then check applicable iOS authorization and selected calendars or data categories.

Sharing changes are saved locally before the settings request is uploaded. Each device drain re-registers local choices before claiming work, repairing interrupted synchronization. Revocation therefore blocks new local execution even when Hermes still shows an old grant.

On registration, permission removal or calendar, Health, or contact scope changes cancel affected server requests and clear their stored results. The processor checks permission and selection again after native work before returning data. Writes check permission and expiry immediately before saving to EventKit.

There is no per-action approval dialog in this execution path once write access is granted. Revocation cannot undo a write already committed or retract data already returned to the model. Server records and local choices are keyed by profile name, not the filesystem-identity binding used by some other Cadu plugins; do not assume that recreating the same profile name automatically invalidates its sharing.

## [Requests, results, and retries](https://cadu.bot/docs/device-access#requests)

1.  The server saves a request as `pending`, normally expiring five minutes after creation. It allows at most 32 pending or running requests per profile.
2.  A paired device claims up to four requests at a time. Each claim gets a new lease with a 35-second lease window; unfinished work can be claimed again after that window.
3.  The device executes locally and uploads the result with its pairing token and lease. The server validates profile, device, current grant, request state, and matching lease.
4.  Agent operation calls wait up to 20 seconds by default. A pending response is not completion; poll the existing request ID instead of submitting the same action again.

Terminal states are `succeeded`, `unavailable`, `failed`, `expired`, and `cancelled`. Inspect the result's `observed_at`, data-specific timestamps, and truncation flags before using it. A result also carries the latest server-synced access information, which may differ from the scope under which an older result was collected.

### Write deduplication

Writes require an idempotency key of 1–128 characters. Reusing it with the same operation and arguments returns the original request; conflicting reuse is rejected. After request cleanup, a retained hash prevents that key from creating another action.

The device journals successful write results by installation and request ID. Created events and reminders also carry a `cadu://device-action/…` URL marker that the provider searches for on retry. These checks address lost result uploads, but are not a general exactly-once guarantee across deleted local state, changed native objects, or every crash boundary. Never generate a new key merely to retry an uncertain write.

## [Data scope and limits](https://cadu.bot/docs/device-access#data)

### Calendar and Reminders

Calendar and Health ranges must have an end after the start and span at most 31 days. Event reads return at most 50 events, sorted by start, with a truncation flag. Event results include title, dates, calendar name/ID, and all-day state. Creation chooses an explicitly requested shared writable calendar, the shared device default, or the only shared writable calendar; otherwise it requires a choice.

Reminder reads return up to 50 incomplete reminders sorted by title. There is no per-list sharing selector in this implementation. Creation uses the writable default list. Completion resolves the supplied identifier directly; the code does not require proof that it appeared in a previous tool result.

### Health

Shared metrics are steps, sleep, average heart rate, and workouts. Steps include a total and daily buckets in the device time zone. Sleep duration merges overlapping asleep intervals within the requested range. Workouts return activity-type IDs, dates, and durations; more than 500 matching workouts requires a smaller range. Sleep queries reject results reaching the 10,000-sample limit.

Health reads require protected device data to be available. Unshared categories are omitted. Null means no readable data, not zero and not proof that permission was denied. This is access to the device's readable HealthKit data, not a medical interpretation or a guarantee of completeness.

### Contacts

A nonempty query is required. Search returns five matches by default, capped at ten, with iOS limited-contact access respected. Names and, when present, organization names accompany matches. Phone, email, birthday, and postal-address selections control which details are returned. Each match is capped at five phone numbers, five emails, and three addresses. There is no bulk-list operation, but these bounds are per request, not a cumulative disclosure quota.

Search criteria are not restricted by those detail selections. An agent with any contact-detail access can search by a candidate email address or phone number even when that field is not shared. A matching result can reveal the person's name and confirm that association without returning the address or number itself. Disable contact sharing for that agent to prevent these lookups.

### Location

Location requires Cadu to be active; background location is not enabled by this provider. It requests approximately hundred-meter desired accuracy, waits up to eight seconds, and accepts a fix only with nonnegative reported accuracy and a timestamp within two minutes. Desired accuracy is not a guaranteed precision. The response includes actual accuracy and timestamp.

## [Background wake and follow-up](https://cadu.bot/docs/device-access#background)

While Cadu is connected in the foreground, it drains requests for locally configured profiles on the selected installation, with a three-second sleep between drain passes. Connections capture the selected endpoint, credentials, and trust policy so switching installations does not redirect an in-flight request.

With push configured, the plugin can ask the relay to wake the exact paired device. The silent APNs payload contains a `device_refresh` marker and install identifier, not the requested operation, prompt, or device data. The relay coalesces wake requests within a 20-minute window in memory. APNs uses a low-priority background notification with a five-minute expiry; delivery is best effort.

A background refresh resolves one saved installation, authenticates directly with Hermes, and uses a bounded drain budget. It does not establish or reuse an old SSH tunnel; SSH-backed instances need Cadu open. Native callback queries have deadlines and cancellation handling. Locked protected data, denied OS permissions, or foreground-only location can still make an operation unavailable.

For a pending request, `notify_when_ready` defaults to true. If push configuration and the push platform are available, the plugin records a deferred reply. The dashboard reconciler checks every five seconds and schedules a one-shot Hermes job for one minute later once the request finishes or expires. The job reads the existing result and delivers through push. This is scheduled agent work, not a guaranteed one-minute arrival or automatic retry of the device operation.

## [Data storage and encryption boundaries](https://cadu.bot/docs/device-access#encryption)

**Device results are not end-to-end encrypted against Hermes.** Results travel over the configured authenticated dashboard connection and are returned to agent tools. Connection security depends on the saved transport and trust policy. The bridge does not add an application-level encryption envelope to those results.

The server stores device registrations, selected scope, arguments, results, and deferred-reply metadata in a device-access database under the installation root. This is ordinary SQLite, not an encrypted database. The database is chmodded to `0600`; its parent is created with mode `0700`. The server stores a SHA-256 hash of the pairing token and compares it when the device claims or completes work.

The device's pairing token is kept in Keychain. Local sharing choices and successful-write receipts are stored in UserDefaults. This code has no timed cleanup for the local write journal.

Server cleanup removes request payloads older than 24 hours from creation when expiry maintenance runs, retaining hashes of used write keys. It is not a strict wall-clock deletion job or a secure-erasure promise. Tool results can also remain in Hermes conversation history, model-provider processing, backups, or downstream outputs; bridge cleanup does not delete those copies.

The API relies on host dashboard authentication. Claiming and completing requests additionally require `X-Cadu-Device-Token`; result reads use dashboard authentication and profile scope. These checks do not hide returned data from the Hermes host or prevent an authorized agent from using information it has already received.

## [API and troubleshooting](https://cadu.bot/docs/device-access#reference)

Routes are mounted under `/api/plugins/cadu-device`, with a `profile` query parameter. Setup reports the protocol, plugin version, and existing profile names.

Device Access routes
| Method | Route | Purpose |
| --- | --- | --- |
| GET | `/setup` | Read compatibility and profile information |
| GET | `/devices` | List synchronized devices and grants |
| PUT | `/devices/{device_id}` | Register device credentials, grants, and scope |
| POST | `/devices/{device_id}/pending` | Claim work with a pairing token |
| POST | `/devices/{device_id}/results/{request_id}` | Upload a leased result with a pairing token |
| GET | `/requests/{request_id}` | Read a profile-scoped result |

If no device has access, enable the specific agent's sharing in Cadu; repeated tool calls cannot grant it. After changing selections, refresh `cadu_devices` and allow the phone to synchronize. An empty event list alone does not establish missing permission.

For pending or expired requests, check the Hermes connection and open Cadu. Unlock for Health or Contacts, and keep the app active for location or SSH connections. For an uncertain write, inspect Calendar or Reminders and retrieve the original request rather than submitting a new action key. If setup reports installed files but unavailable routes, complete the dashboard and gateway restart.
