fixture modeChain is mocked with three demo agents. Set THESEUS_RPC_URL to read from a Theseus node.

Docs · Reference

Credential format.

A Proof of Agenthood credential is a compact JWS. The protected header names the signing key. The payload is a JSON object whose fields are the signed truth.

The wire format

Credentials are serialized as JWS Compact Serialization: three base64url segments joined by dots. The protected header pins the algorithm and the issuer's key ID. The payload carries the claims. The signature is over both.

<base64url(header)>.<base64url(payload)>.<base64url(signature)>

Protected header

{
  "alg": "EdDSA",
  "kid": "theseus-poa-2026-04",
  "typ": "poa+jws"
}

alg is fixed at EdDSA over Ed25519. kid identifies the issuer key; the matching public JWK is published at /poa/.well-known/jwks.json, so verifiers do not need an out-of-band key. typ is poa+jws, distinct from a generic JWT, so consumers do not accidentally treat the payload as one.

Signed claims

type PoACredentialClaims = {
  iss: "theseus.network/poa";
  sub: SS58Address;            // the agent's SS58 address
  jti: string;                 // unique credential id
  iat: number;                 // issued-at, unix seconds
  attestation: Attestation;    // controller-attested or snapshot
  agent: AgentSnapshot;        // the chain snapshot at iat
  policy: {
    revocationListUrl: string;
    refreshHint: "event-driven";
  };
};
  • iss. Fixed string theseus.network/poa. Verifiers can use this to scope which credentials they accept.
  • sub. The agent's SS58 address (Substrate address, prefix 42). Every credential is bound to one agent.
  • jti. Unique credential id. The same agent can have one or more credentials over time. The active credential is the one surfaced at /poa/<agentId>.
  • iat. Issued-at, in unix seconds. Combined with the embedded snapshot block height, this lets a verifier reason about freshness.
  • policy.revocationListUrl. Where to fetch the revocation list (currently /poa/api/revoked). A credential is signature-valid even if revoked. Freshness is a separate axis.

The agent snapshot

The largest payload field is agent: an AgentSnapshot taken from pallet_agents at the moment of issuance.

type AgentSnapshot = {
  agentId: SS58Address;
  name: string;
  summary?: string;
  abgHash: string;             // content hash of the ABG
  abgVersion: number;          // monotonically increasing
  sovereign: boolean;          // false at alpha (chain has no primitive yet)
  controller: SS58Address | null;
  capabilities: {
    models: string[];
    tools: string[];
    intentTypes: string[];
    subAgents: SS58Address[];
  };
  registration: {
    atBlock: number;
    registrar: SS58Address;
  };
  funding: {
    seusBalance: string;       // decimal string, base unit
    active: boolean;
  };
  recentRuns: {
    sampledRuns: number;
    inferenceMix: { kzg: number; signatureOnly: number };
    grade: "full" | "mixed" | "lite" | "unknown";
  };
  enclaveBound: boolean;
  snapshotAtBlock: number;
  snapshotAtTime: string;      // ISO-8601
};

Attestation kinds

The attestation field is a tagged union with two variants:

type Attestation =
  | { kind: "snapshot" }
  | {
      kind: "controller-attested";
      controller: SS58Address;
      nonce: string;
      controllerSig: string;   // hex sr25519 signature over poa:<agentId>:<nonce>
      signedAt: number;
    };

Against the live chain, every credential is controller-attested. The snapshot variant is reserved for the future sovereign-agent path (no controller exists, so no signature is possible) and the demo and fixture environment.

Verification grade

recentRuns.grade compresses how much of the agent's recent inference activity ran through full TensorCommit (KZG) provers versus signature-only lite provers:

  • full. All sampled runs went through KZG provers.
  • mixed. Some KZG, some lite.
  • lite. Entirely signature-only. No integrity guarantee on the model output.
  • unknown. No recent runs sampled.

Verifiers that care about output integrity should refuse lite agents. The grade is signed into the credential, but it is computed at iat. For a live answer, fetch a fresh snapshot.

What is not signed

Two pieces of useful information sit alongside the credential but are not in the JWS:

  • Bundles. The human-readable groupings of intent types shown on the credential page. Bundles can be re-derived from capabilities.intentTypes and may evolve over time. Programmatic consumers should gate on the raw intent strings.
  • Revocation status. Fetched separately from /poa/api/revoked. A signed credential is not self-invalidating; the issuer's revocation list is the source of truth for freshness.