Skip to content

ADPP v1 Semantics (normative) ​

This document defines the normative semantics of the Anolis Device Provider Protocol (ADPP) v1.

Conformance levels. A provider's conformance is stated as ADPP v1/L<n> — the v1 wire contract plus a cumulative semantic bar. Level 1 (L1, the original bar) is everything in this document not tagged. Level 2 (L2) is L1 plus every requirement tagged [L2]; further levels are likewise additive. The wire contract is identical across levels — a level only tightens behavior.

Levels are opt-in. A provider declares its level in its conformance manifest (conformance_level, default 1) and SHOULD advertise it in Hello metadata (conformance_level, absent ⇒ 1). The conformance harness runs the requirements up to the declared level — all gating at that level (there is no separate non-gating phase). Introducing a new level does not invalidate lower-level providers; a plain "ADPP v1 conformant" claim means at least L1. Raising the minimum level required of all ADPP v1 providers is a breaking change — see versioning.md.

When a provider both advertises conformance_level in Hello metadata and declares one in its manifest, the two MUST agree, and the value MUST be a level the verifier implements. A verifier MUST reject a declared level it does not implement rather than silently testing a lower bar.


1. Roles and responsibilities ​

1.1 Anolis (client) ​

Anolis is the ADPP client. It is responsible for:

  • Orchestrating machine behavior (e.g., Behavior Trees)
  • Arbitrating manual vs autonomous control
  • Enforcing safety policies and operating modes
  • Persisting telemetry, state, and events
  • Exposing external APIs (HTTP, dashboards, etc.)

1.2 Provider (server) ​

A Provider is an ADPP server that exposes devices backed by some implementation (I2C/CRUMBS, GPIO, BLE, simulation, etc.).

A Provider is responsible for:

  • Discovering and reporting devices (when applicable)
  • Describing device capabilities (functions and signals)
  • Executing reads and calls
  • Managing backend-specific mechanics:
    • staged reads
    • caching
    • retries and backoff
    • bus scanning and filtering
  • Reporting provider and device health

Providers MUST NOT encode orchestration policy.

Providers may defensively validate inputs and reject invalid or unsafe device-level actions, but Anolis is the sole authority for:

  • Control modes (AUTO, MANUAL, IDLE, SAFE, etc.)
  • Manual control leases
  • Cross-device safety interlocks
  • Behavior execution semantics

2. Transport and framing ​

ADPP messages are defined in proto/anolis/deviceprovider/v1/envelope.proto using Request and Response envelopes.

ADPP itself is transport-agnostic. When used over a byte-stream transport (recommended for local IPC):

  • Each message MUST be framed using a length prefix followed by serialized Protobuf bytes.
  • The length prefix SHOULD be:
    • an unsigned varint, or
    • a fixed-width 32-bit unsigned integer, by mutual agreement.
  • Implementations MUST handle fragmentation and coalescing (a stream may deliver partial or multiple frames).

Rationale: Protobuf does not define framing; length-prefixed framing is a well-established pattern.

The Anolis runtime's concrete binding of this agreement — fixed uint32_le framing, the frame-size limit, Hello metadata, and malformed-stream behavior — is specified normatively in profiles/framed-stdio-v1.md.


3. Session handshake ​

3.1 Hello ​

  • The client SHOULD send HelloRequest as the first message.
  • The provider MUST respond with HelloResponse.
  • The client MUST validate HelloResponse.protocol_version matches the requested protocol version and fail session setup if it does not.
  • If the requested protocol_version is not supported, the provider MUST respond with:
    • CODE_FAILED_PRECONDITION, or
    • CODE_UNIMPLEMENTED.

3.2 Requests before Hello ​

  • [L2] A provider that receives any request other than HelloRequest before a successful Hello exchange MUST reject it with CODE_FAILED_PRECONDITION and MUST NOT process it. (The client SHOULD send Hello first per §3.1; this defines the provider's behavior if it does not.)

4. Request / response correlation ​

  • request_id MUST be unique per in-flight request within a session.
  • Providers MUST echo the same request_id in the corresponding Response.
  • Providers MAY process requests concurrently.
  • Responses MAY arrive out of order.

Clients MUST correlate responses using request_id and MUST NOT assume ordering.


5. Inventory and identity ​

5.1 Device identity ​

  • Device.device_id MUST be stable for the lifetime of the Provider process.
  • Providers SHOULD preserve device_id stability across restarts when feasible.
  • Uniqueness is scoped to the Provider; Anolis SHOULD treat {provider_name, device_id} as globally unique.

5.2 Discovery ​

  • Providers MAY support discovery (e.g., scanning an I2C bus).
  • ListDevices MUST return the currently known devices.
  • Non-discoverable backends MAY return a static set or an empty list.

5.3 Backend filtering ​

All bus scanning, filtering, probing, or identification logic is provider-internal.

Example: a CRUMBS Provider may scan all I2C addresses and only report devices that respond to a CRUMBS identify handshake.


6. Capabilities ​

6.1 DescribeDevice ​

  • DescribeDevice MUST return the complete CapabilitySet for the device.
  • Function IDs and signal IDs MUST be stable for a given device type/version.

6.2 Function identifiers ​

  • function_id is the preferred stable identifier.
  • function_name is a human-readable convenience.
  • If both are provided in a CallRequest, the provider MUST prefer function_id.

6.3 Policy metadata ​

FunctionPolicy fields are informational hints for Anolis and UIs.

In v1:

  • Anolis is the authoritative enforcer of modes and leases.
  • Providers MAY defensively reject calls that violate policy metadata, but Anolis MUST NOT rely on provider enforcement.

7. Telemetry reads ​

7.1 ReadSignals behavior ​

  • Providers MAY return cached values, live values, or a combination.
  • [L2] In a CODE_OK ReadSignalsResponse — including the partial-success form of §7.4 — every SignalValue MUST set timestamp (field present, a valid google.protobuf.Timestamp at the observation time) and MUST set quality to a value defined by the current schema other than QUALITY_UNSPECIFIED. Error responses carry no telemetry and are exempt. (Level 1: timestamp was SHOULD; quality was not mandated.)
  • If a value exceeds SignalSpec.stale_after_ms, providers SHOULD report QUALITY_STALE.

7.2 Default signals ​

For any device type that declares at least one signal, a provider MUST define a stable, non-empty subset of signals designated as default signals.

  • Default signals are returned when ReadSignalsRequest.signal_ids is empty.
  • A default read (empty signal_ids) on a device that declares signals MUST therefore return a non-empty set of values whose signal_ids are a subset of the declared signals. (The subset is provider-curated and need not be the full declared set.)
  • Default signals SHOULD represent low-cost, routinely useful telemetry suitable for dashboards and polling loops.
  • Expensive or rarely-used signals SHOULD NOT be default.

This requirement ensures predictable behavior across providers.

7.3 Freshness hints ​

If ReadSignalsRequest.min_timestamp is provided:

  • Providers SHOULD attempt to satisfy the freshness requirement.
  • If not feasible, providers SHOULD return the best available values and indicate staleness via quality and/or Status.details.
  • min_timestamp is a hint that constrains quality, not success: a provider MUST NOT treat an unmet freshness hint as a hard deadline. An otherwise- readable signal MUST NOT, on account of the hint alone, be turned into an error response (notably CODE_DEADLINE_EXCEEDED); the read returns CODE_OK with the best available values. (A genuine read failure may still return CODE_UNAVAILABLE etc.) (Clarification: previously unstated; a provider that returned an error solely because the hint was unmet is non-conformant under this rule.)

7.4 Missing signals ​

If a requested signal is unknown, providers MUST choose one consistent behavior:

  • Fail the request with CODE_NOT_FOUND, or
  • Return partial results and omit unknown signals.

(Recommended for v1: fail with CODE_NOT_FOUND to simplify client logic.)


8. Function calls ​

8.1 Execution model ​

  • Providers MAY implement calls synchronously.
  • Providers MAY accept calls asynchronously and return an operation_id.

ADPP v1 does not define an operation lifecycle API. If async calls are used, providers MUST document how results are observed (typically via signals).

(Recommended for v1: synchronous execution.)

When a function declares results (FunctionSpec.results), a synchronous successful call (CODE_OK with no operation_id) MUST populate CallResponse.results, keyed by the declared ArgSpec.names — a provider that declares results MUST NOT return an empty result map for a completed call. Asynchronous acceptance (CODE_OK with operation_id set) observes results later (per above), so it carries no such obligation.

8.2 Idempotency ​

  • idempotency_key is an optional retry hint.
  • Providers MAY ignore it in v1.
  • If honored, providers SHOULD treat repeated calls with the same key as safe replays.

8.3 Validation ​

Providers MUST validate device existence, function existence, required arguments, argument types, and declared numeric bounds.

Status codes by category:

  • An unknown device identifier, or the selected function identifier (the function_id when nonzero, otherwise the resolved function_name), → CODE_NOT_FOUND (all levels). When function_id is nonzero, function_name is not selected and is not validated (it is ignored per §6.2).
  • A value outside its declared bounds:
    • L1: → CODE_INVALID_ARGUMENT or CODE_OUT_OF_RANGE.
    • [L2]: → MUST be CODE_OUT_OF_RANGE. L2 only tightens the L1 choice, so an L2 provider also satisfies L1 — the levels stay cumulative.
  • Every other invalid input (wrong type, missing required argument) → CODE_INVALID_ARGUMENT (all levels).

Numeric value rules ([L2]):

  • Declared numeric bounds (ArgSpec.min_* / ArgSpec.max_*) are inclusive: a value equal to a bound is valid; a value strictly outside is out of range.
  • A VALUE_TYPE_DOUBLE argument that carries a declared bound MUST be finite. A non-finite value (NaN, +Inf, -Inf) is malformed input and MUST be rejected with CODE_INVALID_ARGUMENT. This finiteness check precedes the bound check — so +Inf is CODE_INVALID_ARGUMENT, never CODE_OUT_OF_RANGE, even though it numerically exceeds any maximum.

8.4 Deadlines ​

Deadline support is the [L2] deadline capability contract. A provider advertises it via the Hello metadata key supports_deadlines ("true" / "false"; absent ⇒ "false"). At L1, deadline behavior is unconstrained: a provider MAY ignore CallRequest.deadline entirely, and supports_deadlines carries no obligation. An L2 provider that advertises supports_deadlines="true" takes on the contract below; the deadline then applies to every call it accepts:

  • [L2] A malformed deadline (not a valid google.protobuf.Timestamp) → CODE_INVALID_ARGUMENT.
  • [L2] If the deadline is already expired when the request is validated, the provider MUST return CODE_DEADLINE_EXCEEDED and MUST NOT invoke the function (no side effects).
  • [L2] If the deadline expires during synchronous execution, the provider MUST make a documented best-effort to cancel and MUST return CODE_DEADLINE_EXCEEDED. Because physical actuation can be irreversible once issued, side effects MAY have occurred; the provider SHOULD report what is known via Status.details. "Abort" here is best-effort, not a guarantee of no effect.

CODE_OK means done — except for accepted async calls. A provider MUST NOT report CODE_OK for synchronous work it did not complete. For an asynchronous call (§8.1), CODE_OK with CallResponse.operation_id populated means the call was accepted (not yet complete); for such calls the deadline governs acceptance, and results are observed later (typically via signals). This preserves §8.1.


9. Health reporting ​

9.1 Provider health ​

  • GetHealth MUST return ProviderHealth.
  • ProviderHealth.state reflects the provider’s overall ability to service requests.

9.2 Device health ​

  • DeviceHealth reflects reachability and freshness.
  • last_seen SHOULD be updated whenever the provider successfully communicates with the device backend.

10. Error handling ​

  • Every Response MUST include a Status.
  • CODE_OK indicates success.
  • Errors SHOULD include a human-readable message.
  • Status.details MAY include structured diagnostic metadata.

11. Backward compatibility ​

  • Unknown fields MUST be ignored (standard Protobuf behavior).
  • New optional fields may be added within v1.
  • Incompatible wire changes require a new major version (v2).
  • Semantic tightening (a stricter MUST) is introduced as a new, opt-in conformance level within the current major version (see the Conformance levels note above and versioning.md). Raising the minimum level required of all ADPP v1 providers is itself a breaking change and requires a major version.

12. Security considerations (non-normative) ​

ADPP v1 does not define authentication.

For local IPC, rely on OS-level permissions (e.g., Unix socket ownership). For network transports, ADPP SHOULD be wrapped in an authenticated and encrypted channel appropriate to the deployment.