> ## Documentation Index
> Fetch the complete documentation index at: https://hs-df36fa00.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Core Concepts: Providers, Agents, Gateway, and Receipts

> Learn the key abstractions in Watt ServiceNet: providers, agents, the execution gateway, receipts, auth contexts, health, trust, and distributed federation.

Watt ServiceNet is built around six interlocking abstractions: **providers** anchor agent identity, **agents** carry the A2A-compatible capability description, the **gateway** enforces policy and routes calls, **receipts** produce a tamper-evident audit trail, **health and trust records** expose performance signals and blocklist controls, and the **P2P network** replicates all of it across nodes without a central registry. Understanding these concepts will help you design systems that are secure, auditable, and ready to federate.

## Providers

A provider is the top-level identity record in ServiceNet. Every agent you publish must belong to a registered, active provider.

### Identity fields

| Field          | Type                  | Description                                                                          |
| -------------- | --------------------- | ------------------------------------------------------------------------------------ |
| `provider_id`  | string                | Human-readable, URL-safe identifier (e.g. `acme-labs`). Stable across key rotations. |
| `provider_did` | string                | A `did:key` URI holding the provider's current Ed25519 public key.                   |
| `display_name` | string (optional)     | A human-readable label shown in registry listings.                                   |
| `status`       | `active` \| `revoked` | Active providers can publish and invoke agents. Revoked providers are frozen.        |

### Lifecycle

When you register a provider, ServiceNet records the `provider_id`–`provider_did` binding and sets `status: active`. You can rotate the DID key at any time using `POST /v1/providers/:provider_id/rotate-key` — your `provider_id` and all published agents remain unaffected. To permanently freeze a provider, call `POST /v1/providers/:provider_id/revoke`. Revoked providers remain queryable for audit purposes but cannot be invoked.

### Ownership challenges

When `SERVICENET_REQUIRE_PROVIDER_OWNERSHIP_CHALLENGES=1` is set, registration and key rotation require a signed ownership challenge:

1. Request a challenge with `POST /v1/providers/ownership-challenges`, supplying the `provider_did` and `operation` (`register` or `rotate_key`).
2. Sign the `challenge` string in the response using the Ed25519 private key that corresponds to the `provider_did`.
3. Submit the registration or key rotation request with `ownership_challenge_id` and `ownership_signature` set.

Without challenges enabled, the DID key is recorded as-is with no cryptographic proof of ownership. PostgreSQL-backed deployments enable challenges automatically.

***

## Agents

Agents follow a two-phase lifecycle: **submission** and **publication**.

### Phase 1 — AgentSubmission

You create a submission by calling `POST /v1/agent-submissions`. The request body bundles everything ServiceNet needs to validate and eventually publish the agent:

```json theme={null}
{
  "provider_id": "acme-labs",
  "agent_id": "stripe-agent",
  "version": "0.1.0",
  "agent_card": { ... },
  "deployment": { ... },
  "review": { ... },
  "artifacts": { ... },
  "attestations": { ... }
}
```

ServiceNet validates the attestation signature, checks the binding proof, optionally runs a smoke test, and verifies the schema before auto-approving (or holding for admin review if `SERVICENET_REQUIRE_ADMIN_APPROVE=1` is set). A submission can be in one of the following states: `draft`, `submitted`, `in_review`, `approved`, `rejected`, `suspended`, or `revoked`.

### Phase 2 — PublishedAgent

Once approved, the submission becomes a `PublishedAgent` — a public record that is:

* Queryable via `GET /v1/agents` and `GET /v1/agents/:agent_id`
* Replicable to peer nodes over the P2P gossip layer
* Invokable through the gateway at `POST /v1/agents/:agent_id/invoke`

Published agents carry a status of `approved`, `suspended`, or `revoked`.

### The agent card

The `agent_card` field follows the [Google A2A](https://google.github.io/A2A/) specification. Key fields:

| Field                | Description                                                                           |
| -------------------- | ------------------------------------------------------------------------------------- |
| `name`               | Display name for the agent                                                            |
| `description`        | Human-readable capability summary                                                     |
| `url`                | Base URL of the agent's public endpoint                                               |
| `skills`             | Array of skill objects with `id`, `name`, and `description`                           |
| `securitySchemes`    | OpenAPI-style security scheme definitions (e.g. `{ "oauth2": { "type": "oauth2" } }`) |
| `security`           | Array of security requirement objects referencing scheme names                        |
| `preferredTransport` | Must be `"JSONRPC"` for A2A compliance                                                |
| `protocolVersion`    | A2A protocol version (e.g. `"1.0"`)                                                   |

Declare `{ "none": { "type": "none" } }` in `securitySchemes` and `[{ "none": [] }]` in `security` to mark an agent as publicly invokable without auth.

### Deployment config

The `deployment` object tells the gateway where to send invocations:

```json theme={null}
{
  "runtime": "remote_http",
  "endpoint": {
    "url": "https://stripe-agent.example.com/a2a",
    "protocol_binding": "JSONRPC",
    "protocol_version": "1.0",
    "interaction_protocol": "google_a2a"
  }
}
```

`interaction_protocol` defaults to `google_a2a` if omitted.

### Review profile

The `review` object in your submission (and the published record) carries the policy metadata the gateway enforces at invocation time:

| Field                     | Type                        | Description                                                                                                  |
| ------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `risk_level`              | `low` \| `medium` \| `high` | Determines receipt verification requirements and whether `confirm_risky: true` is mandatory                  |
| `data_classes`            | string array                | Labels for sensitive data the agent accesses (e.g. `["financial", "pii"]`)                                   |
| `destructive_actions`     | string array                | List of operations that cannot be undone (e.g. `["payments.refund"]`)                                        |
| `human_approval_required` | boolean                     | Advisory flag for orchestrators that require human-in-the-loop confirmation                                  |
| `allowed_regions`         | string array                | ISO country codes allowed for invocation. Empty means any region is permitted.                               |
| `cost_per_call_units`     | integer (optional)          | Declared cost in abstract units. The gateway rejects calls that exceed the caller's `max_cost_units` budget. |

***

## The Gateway

The gateway is the policy-enforcement and protocol-translation layer between your callers and the agent's remote endpoint. Every `POST /v1/agents/:agent_id/invoke` call passes through the full preflight sequence before any HTTP request leaves the node.

### Policy enforcement order

The gateway checks conditions in this order and returns `403 Forbidden` at the first failure:

<Steps>
  <Step title="Provider must be active">
    The agent's owning provider must have `status: active`. Revoked providers are rejected immediately.
  </Step>

  <Step title="Provider must not be blocked">
    ServiceNet maintains a separate trust record for each provider. A blocked provider (via `POST /v1/admin/providers/:provider_id/block`) is rejected even if the provider record itself is still active.
  </Step>

  <Step title="Agent must not be blocked">
    Individual agents can be blocked via `POST /v1/admin/agents/:agent_id/block` without revoking the provider.
  </Step>

  <Step title="Auth token or auth context required (if applicable)">
    If the agent card declares non-`none` security schemes, the request must supply either an `auth_token` string or a valid `auth_context_id` UUID.
  </Step>

  <Step title="Region must be allowed">
    If `allowed_regions` is non-empty, the request's `region` field must match one of the listed codes (case-insensitive). Omitting `region` when the list is non-empty is also rejected.
  </Step>

  <Step title="Cost must be within budget">
    If the agent card declares a `cost` field and the request (or node default) sets `max_cost_units`, the agent's cost must not exceed the budget.
  </Step>

  <Step title="High-risk agents require explicit confirmation">
    Agents with `risk_level: high` require `confirm_risky: true` in the request body. This prevents accidental invocation of destructive agents.
  </Step>
</Steps>

### Protocol translation

After preflight passes, the gateway translates the ServiceNet invocation into an A2A JSON-RPC `SendMessage` call:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "<uuid>",
  "method": "SendMessage",
  "params": {
    "taskId": "<optional>",
    "contextId": "<optional>",
    "skillId": "<optional>",
    "message": {
      "role": "user",
      "parts": [
        { "kind": "text", "text": "<message>" },
        { "kind": "data", "data": { } }
      ]
    },
    "extensions": {
      "settlement": { },
      "agent_envelope": { }
    }
  }
}
```

The `Authorization: Bearer <token>` header is set from the resolved auth token. The `A2A-Version` header carries the agent's declared `protocol_version`.

### Synchronous vs. asynchronous invocation

<Tabs>
  <Tab title="Synchronous">
    `POST /v1/agents/:agent_id/invoke` — waits for the agent's HTTP response, records the receipt, and returns the full result in one call. Use this for agents with fast, predictable response times.
  </Tab>

  <Tab title="Asynchronous">
    `POST /v1/agents/:agent_id/invoke-async` — accepts the invocation, records a `running` receipt immediately, and executes the A2A call in the background. Returns the `receipt_id` and `status: "running"` straight away. Poll `GET /v1/receipts/:receipt_id` or use `POST /v1/agents/:agent_id/tasks/:task_id/get` to retrieve the final result.
  </Tab>
</Tabs>

***

## Execution Receipts

ServiceNet creates an `ExecutionReceipt` for every invocation — regardless of whether it succeeds or fails. Receipts give you a tamper-evident, cryptographically anchored audit trail of all agent calls.

### Receipt fields

| Field            | Type                                                  | Description                                                  |
| ---------------- | ----------------------------------------------------- | ------------------------------------------------------------ |
| `receipt_id`     | UUID                                                  | Unique identifier for this invocation record                 |
| `agent_id`       | string                                                | The invoked agent                                            |
| `provider_id`    | string                                                | The agent's owning provider                                  |
| `status`         | `running` \| `succeeded` \| `failed` \| `rejected`    | Final invocation outcome                                     |
| `verification`   | `not_required` \| `pending` \| `verified` \| `failed` | Current verification verdict                                 |
| `request_digest` | string                                                | SHA-256 hex digest of the canonicalized invocation request   |
| `result_digest`  | string (optional)                                     | SHA-256 hex digest of the agent's response body              |
| `started_at`     | ISO 8601                                              | When the gateway began processing the invocation             |
| `completed_at`   | ISO 8601 (optional)                                   | When the invocation finished (absent for `running` receipts) |
| `cost_units`     | integer (optional)                                    | Resolved cost for this invocation                            |

### Verification verdicts

The initial `verification` value is set by risk level:

* **`not_required`** — agent has `risk_level: low`. No further action needed.
* **`pending`** — agent has `risk_level: medium` or `high`. The automated verifier sweep (`POST /v1/verifier/run`) processes pending receipts and advances them to `verified` or `failed`. You can also submit a manual verdict via `POST /v1/receipts/:receipt_id/verify`.

Query receipts by `agent_id`, `provider_id`, or `verification` status:

```bash theme={null}
curl 'http://127.0.0.1:8042/v1/receipts?agent_id=stripe-agent'
curl 'http://127.0.0.1:8042/v1/receipts?provider_id=acme-labs&verification=pending'
curl 'http://127.0.0.1:8042/v1/receipts/:receipt_id/verifications'
```

***

## Auth Contexts

Auth contexts let you store encrypted credentials in ServiceNet and reference them by ID at invocation time, instead of passing raw tokens in every request.

Register an auth context once:

```bash theme={null}
curl -X POST http://127.0.0.1:8042/v1/auth-contexts/register \
  -H 'content-type: application/json' \
  -d '{
    "subject_did": "did:key:z6MkhaXgBZDvotD1X9gRrYkM5Xq9jYQqK6d8r8bQdE1mV2Xa",
    "provider_id": "acme-labs",
    "auth_model": { "mode": "bearer_token" },
    "token": "secret-token"
  }'
```

Then reference the returned `auth_context_id` UUID in invocation requests:

```json theme={null}
{
  "message": "Create a payment link",
  "auth_context_id": "c1d2e3f4-a5b6-7890-cdef-012345678901",
  "region": "AU"
}
```

### Auth context details

* The token is encrypted at rest using the node's `SERVICENET_SECRET_BROKER_KEY`. Only a masked preview (`token_preview`) is returned in API responses — the plaintext is never exposed again.
* Auth contexts are scoped to a `provider_id`. The gateway rejects a context whose `provider_id` doesn't match the target agent's owning provider.
* You can set an optional `expires_at` timestamp. Expired contexts are rejected by the gateway.
* Supported auth models: `bearer_token`, `api_key_header` (with a `header_name`), `capability_token`, and `none`.

<Warning>
  Auth context encryption requires `SERVICENET_SECRET_BROKER_KEY` to be set. Database-backed deployments that omit this variable will fail to start. Keep the key in a secrets manager — losing it makes stored contexts unrecoverable.
</Warning>

***

## Health & Trust

ServiceNet tracks two independent signals for every provider and agent: a **health record** that reflects observed invocation performance, and a **trust record** that carries a reputation score and an explicit blocklist flag.

### Health records

Health records are updated automatically as the gateway processes invocations. You can query them at any time to understand how a provider or agent has been performing.

```bash theme={null}
curl http://127.0.0.1:8042/v1/health/providers
curl http://127.0.0.1:8042/v1/health/agents
```

Each record includes a `status` field with one of the following values:

| Value      | Meaning                                                                         |
| ---------- | ------------------------------------------------------------------------------- |
| `unknown`  | No invocation data has been recorded yet                                        |
| `online`   | Recent invocations are succeeding normally                                      |
| `degraded` | The agent or provider is responding, but with elevated failure rates or latency |
| `offline`  | Recent invocations have all failed or timed out                                 |

### Trust records

Trust records track a `reputation_score` (a floating-point value between 0.0 and 1.0) and a `blocked` flag. A blocked provider or agent is rejected by the gateway even if the provider record itself is still `active`.

```bash theme={null}
curl http://127.0.0.1:8042/v1/trust/providers
curl http://127.0.0.1:8042/v1/trust/agents
```

Use the admin block and unblock endpoints to manage blocklist entries:

```bash theme={null}
# Block an agent
curl -X POST http://127.0.0.1:8042/v1/admin/agents/stripe-agent/block \
  -H 'content-type: application/json' \
  -d '{ "reason": "policy violation" }'

# Unblock an agent
curl -X POST http://127.0.0.1:8042/v1/admin/agents/stripe-agent/unblock
```

***

## P2P Network

ServiceNet includes an optional Iroh-based P2P overlay that replicates provider records and published agents across nodes without a central server. Enable it with `SERVICENET_P2P_ENABLED=1`.

### How it works

Each node generates a persistent Ed25519 keypair that produces a unique `EndpointId`. Peer addresses are expressed as `<endpoint_id>@<ip>:<port>` — there are no libp2p multiaddrs. Iroh handles NAT traversal with QUIC hole-punching and falls back to Iroh's public relay network automatically when direct connectivity fails.

<CodeGroup>
  ```bash First node theme={null}
  SERVICENET_P2P_ENABLED=1 \
  SERVICENET_P2P_NETWORK_ID=devnet \
  SERVICENET_P2P_LISTEN_ADDRS=0.0.0.0:4101 \
  cargo run -p watt-servicenet-node
  ```

  ```bash Second node (joining the first) theme={null}
  SERVICENET_P2P_ENABLED=1 \
  SERVICENET_P2P_NETWORK_ID=devnet \
  SERVICENET_P2P_LISTEN_ADDRS=0.0.0.0:4102 \
  SERVICENET_P2P_BOOTSTRAP_PEERS=<peer-endpoint-id>@127.0.0.1:4101 \
  cargo run -p watt-servicenet-node
  ```
</CodeGroup>

### Gossip and backfill

* **Gossip** — when a provider registers or an agent is approved, the node broadcasts the record to all connected peers.
* **Backfill** — when a new peer connection is established, the node requests a full sync of all known provider and published-agent records from that peer. Backfill is bidirectional, so both nodes converge on the union of their records.

Inbound records pass through the same local registry validation as records submitted via the HTTP API. Only records that pass validation are persisted.

### Federation modes

<Tabs>
  <Tab title="Open (default)">
    Any connected peer can contribute provider and published-agent records. This is the default p2p behavior — appropriate for experimental or community-run networks.

    ```bash theme={null}
    SERVICENET_FEDERATION_MODE=open
    ```
  </Tab>

  <Tab title="Trusted">
    Only peers listed in `SERVICENET_FEDERATION_TRUSTED_PEERS` are accepted for inbound gossip, backfill requests, and backfill responses. Use trusted mode for official or curated entry nodes.

    ```bash theme={null}
    SERVICENET_FEDERATION_MODE=trusted \
    SERVICENET_FEDERATION_TRUSTED_PEERS=<peer-endpoint-id-1>,<peer-endpoint-id-2>
    ```
  </Tab>
</Tabs>

<Note>
  The P2P layer replicates **registry records** (providers and published agents) only. Execution receipts, auth contexts, and moderation cases are local to each node.
</Note>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Register a Provider" icon="id-card" href="/guides/register-provider">
    Full registration flow with ownership challenges and key rotation examples.
  </Card>

  <Card title="Submit an Agent" icon="robot" href="/guides/submit-agent">
    Build a complete agent submission with signed attestations and review profiles.
  </Card>

  <Card title="Invoke an Agent" icon="bolt" href="/guides/invoke-agent">
    Sync and async invocation, auth contexts, cost budgets, and task polling.
  </Card>

  <Card title="P2P Setup" icon="network-wired" href="/guides/p2p-setup">
    Configure multi-node federation with gossip, backfill, and trusted peer allowlists.
  </Card>
</CardGroup>
