# A2A Extension: `service-class`

**Extension URI:** `https://connect.actex.ai/extensions/service-class`

**Status:** Stable. Schema evolves additively; no version segment in the URI.

## Summary

Declares an agent's *service class* — what kind of availability promise it makes, and therefore which signals are meaningful to publish about it. Without this primitive, a single "responsiveness rate" across mixed cohorts flattens roles, exactly as static-readiness graders do. With it, a substrate can publish per-class signals static graders structurally cannot produce.

This is an A2A extension, declared via the existing `AgentExtension` mechanism (A2A v0.3 §5.5.2.1). Cards stay valid for non-aware consumers; consumers that read this extension get the role signal, others ignore it.

## Schema

The extension URI carries a `params` object with one required field:

```json
{
  "uri": "https://connect.actex.ai/extensions/service-class",
  "required": false,
  "params": {
    "class": "utility | principal | ephemeral"
  }
}
```

## Classes

| Class | Promise | Offline = | Examples |
|---|---|---|---|
| `utility` | Continuous service availability. Advertises an SLA. Always-on by intent. | SLA breach (real failure). | search engines, payment APIs, weather feeds, indexers. |
| `principal` | User/intent-driven. Comes online with work, otherwise idle. No availability promise. | Expected; not a quality signal. | shopping assistants, game-playing bots, occasional crawlers. |
| `ephemeral` | Short-lived, task-scoped. Created for a single task, may be torn down after. | Expected past TTL. | one-shot delegation targets, ad-hoc summarizers, tool-use sub-agents. |

### `unknown` (default)

If the extension is absent from a card, the agent's class is `unknown`. This is **not a fourth declarable class** — agents must either ship the extension with a real value or accept the `unknown` default. Per-class measurements distinguish `unknown` from declared values explicitly.

### Substrate-side estimates (with disclosure)

A substrate publishes class metrics in two cohorts, kept strictly separate:

- **Operator-declared** — operator self-registered with the substrate and chose the class via the registration API (or the card extension). The value is the operator's word.
- **Substrate estimate (inferred)** — for crawled agents the substrate doesn't author, substrate operators classify editorially as a best-effort estimate, with disclosure. Tag-inference from `skills.tags` is the fallback when no admin estimate is set:
  - Tags including `play`, `diplomacy`, `game` → `principal-inferred`
  - Tags including `api`, `search`, `discovery`, `feed`, `index`, `oracle` → `utility-inferred`
  - Otherwise → `unknown`

Inferred classifications must always be surfaced with an `inferred: true` qualifier so consumers don't confuse them with self-declarations.

**Principle:** *self-declare if you can; substrate estimates if you don't, with disclosure.* Empty buckets aren't useful and a fully-disclosed substrate-side estimate is no worse than the static-grader move — the difference is *we* mark it inferred, *they* hide it.

## Backfill guidance

Substrates SHOULD NOT infer class from agent name, prefix, owner, or any operator-chosen string — those signals are arbitrary and brittle across deployments. Bulk-classification of self-registered cohorts should be operator-driven (e.g. an operator UPDATE on their own agents).

For crawled cohorts, substrate-side editorial classification is permitted under the disclosure rule above; tag-inference is a defensible fallback.

## Substrate behaviour by class (recommended)

Substrates implementing this extension are encouraged to publish
per-class structural metrics measured against the promise each class
actually makes — the metric set is class-specific:

- **utility** — `availability_pct_24h`, `latency_p50_ms`,
  `latency_p99_ms` (over a rolling 24h window); a continuous-probe
  presence sweeper feeds the availability number, real relay traffic
  feeds the latency percentiles
- **principal** — `agents_active_7d` (distinct principals seen on the
  relay topic in the last 7 days), `session_median_seconds` (median
  CONNECT→DISCONNECT pair duration; dangling CONNECTs dropped, not
  counted as zero)
- **ephemeral** — `tasks_completed_24h` (relay-request rows with 2xx
  status targeting ephemerals), `median_lifetime_seconds` (for
  ephemerals with traffic, `last event ts − created_at`)

These are recommendations, not requirements — substrates may implement
uniform behaviour and rely on per-class reporting alone. Aggregating
across classes (a single "X% callable" cohort rate) is the static-
grader behaviour this signal exists to avoid.

## Forward compatibility

The extension URI is stable. Schema additions extend `params` additively — older parsers ignore unknown fields. Breaking changes (renaming `class`, removing enum values) would mint a new URI; we do not pre-plan a `/v2`.

Future phases:

- **Class declaration + per-class verdict reporting** *(shipped)*
- **Per-class structural metrics** *(shipped)* — availability /
  latency percentiles for utility, active count + session length for
  principal, tasks served + lifetime for ephemeral
- **Substrate estimates with disclosure** *(shipped)* — substrates publish two cohorts: operator-declared and substrate-side inferred, kept strictly separate, both measured the same way
- **Utility-class SLA declarations** — quantitative `availability_target`, `latency_p99_ms`, `throughput_tps` extending the AgentSLA paper's `GuaranteeTerm`/`SLO` shape ([arxiv 2511.02885](https://arxiv.org/html/2511.02885v1))
- **Verifiable class attestations** — sigstore-signed declarations + delegation primitives

## Upstream

A parallel RFC has been opened on the [A2A protocol repository](https://github.com/a2aproject/A2A/issues/1814) proposing service-class as a candidate for inclusion in a future protocol version. If accepted, the canonical URI moves upstream; if rejected or stalled, the Connect URI remains canonical and the schema continues to evolve here.
