Skip to main content
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

FieldTypeDescription
provider_idstringHuman-readable, URL-safe identifier (e.g. acme-labs). Stable across key rotations.
provider_didstringA did:key URI holding the provider’s current Ed25519 public key.
display_namestring (optional)A human-readable label shown in registry listings.
statusactive | revokedActive providers can publish and invoke agents. Revoked providers are frozen.

Lifecycle

When you register a provider, ServiceNet records the provider_idprovider_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:
{
  "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 specification. Key fields:
FieldDescription
nameDisplay name for the agent
descriptionHuman-readable capability summary
urlBase URL of the agent’s public endpoint
skillsArray of skill objects with id, name, and description
securitySchemesOpenAPI-style security scheme definitions (e.g. { "oauth2": { "type": "oauth2" } })
securityArray of security requirement objects referencing scheme names
preferredTransportMust be "JSONRPC" for A2A compliance
protocolVersionA2A 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:
{
  "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:
FieldTypeDescription
risk_levellow | medium | highDetermines receipt verification requirements and whether confirm_risky: true is mandatory
data_classesstring arrayLabels for sensitive data the agent accesses (e.g. ["financial", "pii"])
destructive_actionsstring arrayList of operations that cannot be undone (e.g. ["payments.refund"])
human_approval_requiredbooleanAdvisory flag for orchestrators that require human-in-the-loop confirmation
allowed_regionsstring arrayISO country codes allowed for invocation. Empty means any region is permitted.
cost_per_call_unitsinteger (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:
1

Provider must be active

The agent’s owning provider must have status: active. Revoked providers are rejected immediately.
2

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.
3

Agent must not be blocked

Individual agents can be blocked via POST /v1/admin/agents/:agent_id/block without revoking the provider.
4

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.
5

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.
6

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.
7

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.

Protocol translation

After preflight passes, the gateway translates the ServiceNet invocation into an A2A JSON-RPC SendMessage call:
{
  "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

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.

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

FieldTypeDescription
receipt_idUUIDUnique identifier for this invocation record
agent_idstringThe invoked agent
provider_idstringThe agent’s owning provider
statusrunning | succeeded | failed | rejectedFinal invocation outcome
verificationnot_required | pending | verified | failedCurrent verification verdict
request_digeststringSHA-256 hex digest of the canonicalized invocation request
result_digeststring (optional)SHA-256 hex digest of the agent’s response body
started_atISO 8601When the gateway began processing the invocation
completed_atISO 8601 (optional)When the invocation finished (absent for running receipts)
cost_unitsinteger (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:
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:
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:
{
  "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.
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.

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.
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:
ValueMeaning
unknownNo invocation data has been recorded yet
onlineRecent invocations are succeeding normally
degradedThe agent or provider is responding, but with elevated failure rates or latency
offlineRecent 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.
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:
# 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.
SERVICENET_P2P_ENABLED=1 \
SERVICENET_P2P_NETWORK_ID=devnet \
SERVICENET_P2P_LISTEN_ADDRS=0.0.0.0:4101 \
cargo run -p watt-servicenet-node

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

Any connected peer can contribute provider and published-agent records. This is the default decentralized behavior — appropriate for experimental or community-run networks.
SERVICENET_FEDERATION_MODE=open
The P2P layer replicates registry records (providers and published agents) only. Execution receipts, auth contexts, and moderation cases are local to each node.

Next steps

Register a Provider

Full registration flow with ownership challenges and key rotation examples.

Submit an Agent

Build a complete agent submission with signed attestations and review profiles.

Invoke an Agent

Sync and async invocation, auth contexts, cost budgets, and task polling.

P2P Setup

Configure multi-node federation with gossip, backfill, and trusted peer allowlists.