| 1 |
# Client API Stability |
| 2 |
|
| 3 |
The contract between EveryCycle and everything that talks to it. Compatibility shims, third-party clients, distro defaults, and operator tooling all depend on this commitment being honored. |
| 4 |
|
| 5 |
This document is the policy. The crate that defines the surface is `everycycle-api`. |
| 6 |
|
| 7 |
## The freeze gate |
| 8 |
|
| 9 |
The API is **explicitly unstable** until the v1 freeze. |
| 10 |
|
| 11 |
The freeze trips when **both** are true: |
| 12 |
|
| 13 |
1. Thread A has reached A4 (cross-vendor activation handoff exercised in practice; the trait shape has survived contact with a second vendor). |
| 14 |
2. A second external client has shipped against the API and is in production use. |
| 15 |
|
| 16 |
Whichever condition lands last trips the gate. A date does not trip the gate. |
| 17 |
|
| 18 |
Until the gate trips: |
| 19 |
|
| 20 |
- The crate constant `API_UNSTABLE` is `true` and `API_VERSION` is `"0"`. |
| 21 |
- Any release may break the wire format, the type shapes, the IPC framing, or the socket location. |
| 22 |
- Clients should pin to an exact daemon version and refuse to connect to a daemon whose advertised version differs. |
| 23 |
- Breakage is announced in the release notes for the breaking release. There is no migration window pre-freeze. |
| 24 |
|
| 25 |
When the gate trips: |
| 26 |
|
| 27 |
- `API_VERSION` becomes `"1"`, `API_UNSTABLE` becomes `false`. |
| 28 |
- The rules in **What "stable" means** below apply. |
| 29 |
|
| 30 |
The gate is one-way. After the freeze trips, the API cannot be re-marked unstable; it can only progress to v2 under the rules below. |
| 31 |
|
| 32 |
## What is in scope |
| 33 |
|
| 34 |
The stability commitment covers: |
| 35 |
|
| 36 |
- The public types in `everycycle-api` (`ClientRequest`, `ServerEvent`, `ModelRef`, `PromptSpec`, `SamplingParams`, `Priority`, `TokenChunk`, `FleetSnapshot`, `ErrorKind`, and everything they transitively contain). |
| 37 |
- The wire format: CBOR over length-prefixed frames as documented in `transport.rs`. |
| 38 |
- The IPC framing constants (`FRAME_LENGTH_BYTES`, `MAX_FRAME_BYTES`). |
| 39 |
- The default socket path (`/run/everycycle/api.sock`) and the `EVERYCYCLE_SOCKET` environment-variable override. |
| 40 |
- The semantics of `RequestKind` variants and the event-stream lifecycle (every request terminates either with an event whose `finished` discriminator is set, or with `ServerEvent::Error`). |
| 41 |
|
| 42 |
## What is out of scope |
| 43 |
|
| 44 |
The stability commitment does **not** cover: |
| 45 |
|
| 46 |
- Internal daemon types, including the `Executor` trait in `everycycle-runtime`. |
| 47 |
- Telemetry schemas (Prometheus metric names, OpenTelemetry attribute keys). These have their own stability story, set per the operator-surface charter when it lands. |
| 48 |
- The HAL inventory schema (`everycycle-hal::inventory::DeviceInventory`). It is consumed by appraise and the TUI but is not part of the client surface; revising it is internal. |
| 49 |
- The probe/appraise schema (`everycycle-hal::capability::*`). Appraisal reports have their own format-version field independent of the API version. |
| 50 |
- Diagnostics in error messages. The `ErrorKind` enum is stable; the `message: String` carried with an error is not. |
| 51 |
- Performance characteristics. Throughput, latency, queueing behavior, and admission policy are tuning concerns, not API concerns. |
| 52 |
|
| 53 |
## What "stable" means after the freeze |
| 54 |
|
| 55 |
Once `API_VERSION` is `"1"`: |
| 56 |
|
| 57 |
**Patch releases** (`1.x.Y`) are bug-fix only. No new variants, no new fields, no behavior changes that a correctly written client would notice. |
| 58 |
|
| 59 |
**Minor releases** (`1.X.0`) are strictly additive: |
| 60 |
|
| 61 |
- New `RequestKind` variants may be added. Existing clients ignore unknown variants in their own events stream by definition (they never sent that request). |
| 62 |
- New `ServerEvent` variants may be added; clients must tolerate unknown event variants by skipping them, and the daemon will not send an unknown-shaped event to a client that hasn't first sent a request of a kind that can produce it. |
| 63 |
- New optional fields may be added to existing types, default-valued for serde compatibility. Required fields may not be added. |
| 64 |
- New `ErrorKind` variants may be added; clients must treat unknown error kinds as `ErrorKind::Internal` for handling purposes. |
| 65 |
- The framing constants and socket path are stable; minor releases do not move them. |
| 66 |
|
| 67 |
**Major releases** (`X.0.0`) are reserved for changes that cannot be expressed under the additive rules above. A major release requires: |
| 68 |
|
| 69 |
- A migration document published with the release notes, with side-by-side before/after for every breaking change. |
| 70 |
- A deprecation window of **at least one full release cycle on every supported distro** before the deprecated surface is removed. "Release cycle" means the cadence of the underlying distro (e.g. Fedora's release cadence), not the daemon's own. |
| 71 |
- The previous major version's daemon binary remains buildable from source for the duration of the deprecation window, so operators can run both side-by-side during migration. |
| 72 |
- A bump in `API_VERSION` to the new major (`"2"`, `"3"`, ...). |
| 73 |
|
| 74 |
The deprecation window is the cost the project pays for breaking the contract. The pricing is intentional. Asking shim authors, distro packagers, and third-party clients to chase a moving API is the failure mode this policy exists to prevent. |
| 75 |
|
| 76 |
## Versioning of `everycycle-api` the crate |
| 77 |
|
| 78 |
The crate version on crates.io follows the API version. `everycycle-api 1.x.y` corresponds to `API_VERSION = "1"`. A pre-freeze `0.x` is permitted but not committed to — the gate decides. |
| 79 |
|
| 80 |
## Versioning of the daemon |
| 81 |
|
| 82 |
The daemon (`everycycled`) has its own semver track that is **independent** of the API version. A given daemon major version may speak multiple API major versions if backward compatibility is feasible; conversely, a daemon major version bump does not imply an API change. |
| 83 |
|
| 84 |
The daemon's advertised API version is what clients negotiate against, not its own version. |
| 85 |
|
| 86 |
## Negotiation |
| 87 |
|
| 88 |
Connection handshake (added when the daemon's listening code lands; described here so the API surface is consistent with it): |
| 89 |
|
| 90 |
1. Client connects to the socket. |
| 91 |
2. Daemon sends a single frame containing its `API_VERSION` string and a list of supported `API_VERSION` strings (for the case where it speaks more than one). |
| 92 |
3. Client either proceeds (its expected version is in the list) or disconnects. |
| 93 |
|
| 94 |
Pre-freeze clients should additionally check `API_UNSTABLE` and refuse to operate against a daemon built from a different commit than they were. |
| 95 |
|
| 96 |
## Out-of-band breaking changes |
| 97 |
|
| 98 |
Security fixes that require a wire-format change are the only carve-out. They are still announced with a migration document but may ship faster than the standard deprecation window allows. Any use of this carve-out is logged in the project's security-disclosure record (which does not exist yet — see `CONTRIBUTING.md`'s "What this file does not cover yet"). |
| 99 |
|