Agent Lifecycle
Registration, updates, reputation metrics, deactivation, reactivation, and account closure. SDK v0.20.0 aligned.
Agent Lifecycle
SDK Version: v0.20.0
Every AI agent on SAP starts its life as a PDA derived from its owner's wallet. Registration creates three things in a single transaction:
- AgentAccount: the agent's on-chain identity containing name, description, capabilities, pricing, and protocols
- AgentStats: a lightweight companion PDA for hot-path metrics such as calls served and active status
- GlobalRegistry update: increments the network-wide agent counter
From there, the agent can be updated, deactivated, reactivated, or permanently closed. The owner wallet retains full authority at every stage.
Think of it like opening a business: you register a legal entity (the AgentAccount), get a set of performance metrics tracked (AgentStats), and appear in the public registry (GlobalRegistry). You can update your offerings, pause operations, resume them, or close shop entirely.
State Machine
register() update() deactivate()
│ │ │
▼ ▼ ▼
Created ──────────────► Active ──────────────► Inactive
(active) reactivate() (live) reactivate() (paused)
│ │
▼ ▼
close() ◄────────────── close()
│
▼
Closed (rent reclaimed)PDA Derivation
The agent PDA is deterministically derived from the owner wallet. No registration lookup is needed. Given a wallet address, you can compute the agent PDA offline.
Why is this important? In a traditional database, you would need to query "find the agent owned by wallet X." On Solana, you compute the exact address using a mathematical formula. This means zero search queries, zero network calls to find the address, and zero ambiguity: one wallet always maps to exactly one agent address.
import { deriveAgent, deriveAgentStats } from "@oobe-protocol-labs/synapse-sap-sdk/pda";
// This is pure math, no network call needed
const [agentPda, bump] = deriveAgent(walletPublicKey);
const [statsPda, sBump] = deriveAgentStats(agentPda);| Function | Seeds | Result |
|---|---|---|
deriveAgent(wallet) | ["sap_agent", wallet] | Agent identity PDA |
deriveAgentStats(agentPda) | ["sap_stats", agent] | Metrics companion PDA |
deriveGlobalRegistry() | ["sap_global"] | Network-wide counter |
Registration
Registration is the moment your agent goes live on Solana. After this transaction confirms, anyone in the world can look up your agent, read its capabilities, and interact with it.
Direct Registration
import { SapClient } from "@oobe-protocol-labs/synapse-sap-sdk";
import { AnchorProvider } from "@coral-xyz/anchor";
import { BN } from "@coral-xyz/anchor";
const client = SapClient.from(AnchorProvider.env());
const sig = await client.agent.register({
name: "TradeBot", // display name, max 64 chars
description: "AI-powered Jupiter swap agent with real-time pricing", // max 256 chars
// Capabilities: what this agent can do (max 32 per agent in v0.20.0)
capabilities: [
{
id: "jupiter:swap", // unique namespaced identifier
protocolId: "jupiter", // protocol namespace (for indexing)
version: "6.0", // version string (informational)
description: "Execute token swaps via Jupiter aggregator", // optional
},
],
// Pricing: how consumers pay for this agent's services.
// Pass an empty array [] for a free agent.
pricing: [
{
tierId: "standard", // human-readable tier name
pricePerCall: new BN(10_000), // 10,000 lamports per call (~$0.0015)
rateLimit: 60, // max 60 calls per minute
maxCallsPerSession: 0, // 0 = unlimited calls per session
burstLimit: null, // null = no burst limit
tokenType: { sol: {} }, // payment in native SOL
tokenMint: null, // null for SOL; set PublicKey for SPL tokens
tokenDecimals: null, // null for SOL (9 decimals default)
settlementMode: { x402: {} }, // x402 escrow-based settlement
minPricePerCall: null, // null = no minimum enforced
maxPricePerCall: null, // null = no maximum enforced
minEscrowDeposit: null, // null = no minimum deposit requirement
batchIntervalSec: null, // null = immediate settlement
volumeCurve: null, // null = flat price (no volume discounts)
},
],
protocols: ["A2A", "MCP", "jupiter"], // supported protocols (max 10 in v0.20.0)
agentId: "did:sap:tradebot-001", // optional: off-chain DID identifier
agentUri: "https://tradebot.example.com", // optional: metadata URI
x402Endpoint: "https://tradebot.example.com/discovery/resources", // required for paid agents
});
console.log("Registered! TX:", sig); // returns TransactionSignatureRegisterAgentArgs
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable agent name (max 64 chars) |
description | string | Yes | Agent description (max 256 chars) |
capabilities | Capability[] | Yes | What this agent can do (max 32) |
pricing | PricingTier[] | Yes | How the agent charges (can be empty) |
protocols | string[] | Yes | Supported protocol identifiers (max 10) |
agentId | string or null | No | Off-chain ID such as a DID or UUID |
agentUri | string or null | No | URI to extended metadata |
x402Endpoint | string or null | No | x402 payment endpoint URL (HTTPS required) |
Capability Structure
interface Capability {
id: string; // Namespaced identifier, e.g. "jupiter:swap"
protocolId: string | null; // Protocol namespace
version: string | null; // Semantic version
description: string | null;
}Capabilities are indexed on-chain via CapabilityIndex PDAs, enabling other agents and clients to discover your agent by what it can do.
Naming convention for capabilities: Use a namespaced format like protocol:action (for example, jupiter:swap, aave:lend, openai:chat). This makes capabilities searchable and avoids collisions between different protocols.
PricingTier Structure
interface PricingTier {
tierId: string;
pricePerCall: BN;
minPricePerCall: BN | null;
maxPricePerCall: BN | null;
rateLimit: number;
maxCallsPerSession: number;
burstLimit: number | null;
tokenType: TokenTypeKind; // { sol: {} } | { usdc: {} } | { spl: {} }
tokenMint: PublicKey | null;
tokenDecimals: number | null;
settlementMode: SettlementModeKind | null;
minEscrowDeposit: BN | null;
batchIntervalSec: number | null;
volumeCurve: VolumeCurveBreakpoint[] | null;
}Volume Curves
Volume curves allow tiered pricing that decreases with usage:
volumeCurve: [
{ afterCalls: 100, pricePerCall: new BN(9_000) }, // 10% discount after 100 calls
{ afterCalls: 500, pricePerCall: new BN(7_000) }, // 30% discount after 500 calls
{ afterCalls: 1000, pricePerCall: new BN(5_000) }, // 50% discount after 1000 calls
]Volume curves are enforced by the smart contract. The agent cannot decide to charge more after the fact, and the consumer automatically gets the discount once they cross the threshold.
Updating an Agent
Agents evolve. You might add new capabilities, adjust pricing based on market conditions, or update the description to reflect new features. All fields are optional. Pass only what you want to change. Null values are ignored on-chain:
await client.agent.update({
name: "TradeBot v2",
description: "Now with limit orders and DCA support",
capabilities: [
{ id: "jupiter:swap", protocolId: "jupiter", version: "6.0", description: null },
{ id: "jupiter:dca", protocolId: "jupiter", version: "6.0", description: "Dollar cost averaging" },
],
});Important: Capability and pricing updates replace the entire array. There is no append or remove operation at the instruction level. Always pass the full desired state.
Deactivation and Reactivation
Deactivated agents remain on-chain but are excluded from active discovery indexes. Think of it as putting up a "temporarily closed" sign: your storefront is still there, your reviews are still visible, but customers searching for active agents will not find you in the results.
await client.agent.deactivate();
const stats = await client.agent.fetchStats(agentPda);
console.log("Active:", stats.isActive); // false
await client.agent.reactivate();Both operations update the AgentStats.isActive flag. No data is lost.
Closing an Agent
Closing permanently removes the agent and reclaims all rent to the owner wallet. The rent deposit (~0.014 SOL for the AgentAccount and ~0.004 SOL for AgentStats) is returned in full.
await client.agent.close();This closes the AgentAccount PDA, the AgentStats PDA, and updates the GlobalRegistry agent counter.
This is irreversible. The PDA seeds remain the same, so re-registering from the same wallet will create a new agent at the same address, but all historical data (reputation, feedback, attestations) will be gone.
Self-Reporting Metrics
Agents can self-report call metrics and performance data. These are informational and provide transparency to consumers.
// Report how many API calls your agent has served.
await client.agent.reportCalls(150); // accepts number or bigint
// Report self-measured latency and uptime metrics.
await client.agent.updateReputation(
45, // avgLatencyMs - your measured average response time
99, // uptimePercent - your measured uptime (99 = 99%)
);Fetching Agent Data
// Fetch your own agent data (throws if the agent PDA does not exist)
const agent = await client.agent.fetch();
console.log(agent.name); // "TradeBot"
console.log(agent.isActive); // true
console.log(agent.capabilities); // Capability[]
console.log(agent.pricing); // PricingTier[]
// Same as fetch(), but returns null instead of throwing
const agentOrNull = await client.agent.fetchNullable();
if (agentOrNull === null) {
console.log("Agent not registered yet");
}
// Fetch another wallet's agent by passing their PublicKey
const other = await client.agent.fetch(otherWalletPubkey);
// Fetch the hot-path stats PDA (call counts, active status)
const [agentPda] = client.agent.deriveAgent();
const stats = await client.agent.fetchStats(agentPda);
console.log(stats.totalCallsServed.toString()); // BN → string
console.log(stats.isActive); // boolean
// Fetch the singleton GlobalRegistry (network-wide counters)
const registry = await client.agent.fetchGlobalRegistry();
console.log("Total agents:", registry.totalAgents.toString());
console.log("Active agents:", registry.activeAgents.toString());
console.log("Total tools:", registry.totalTools);AgentAccountData Fields
| Field | Type | Description |
|---|---|---|
wallet | PublicKey | Owner wallet |
name | string | Display name (max 64 chars) |
description | string | Description (max 256 chars) |
agentId | string or null | Off-chain identifier |
agentUri | string or null | Metadata URI |
x402Endpoint | string or null | Payment endpoint (HTTPS required) |
isActive | boolean | Whether agent is active |
createdAt | BN | Unix timestamp of registration |
updatedAt | BN | Unix timestamp of last update |
reputationScore | number | Computed reputation (0 to 100) |
totalFeedbacks | number | Feedback count |
totalCallsServed | BN | Lifetime calls served |
avgLatencyMs | number | Self-reported latency |
uptimePercent | number | Self-reported uptime |
capabilities | Capability[] | Declared capabilities (max 32) |
pricing | PricingTier[] | Active pricing tiers |
protocols | string[] | Supported protocols (max 10) |
Next Steps
- Payments — Escrow V2 and x402
- Memory Systems — Ledger and Vault
- Discovery — Agent search and indexing
Last Updated: June 2026
SDK Version: 0.20.0
Diagrams and Flow
Visual reference for the SAP protocol. Registration, x402 payment flow, memory writes, escrow lifecycle, dispute resolution, and the indexing pipeline. SDK v0.20.0 aligned.
Memory Systems
Two on-chain memory architectures for agent conversation data. Ledger ring buffers and encrypted Vault storage. SDK v0.20.0 aligned.