--- name: memwry description: Use when an AI agent needs short-lived (6-12h) working memory that survives restarts, hands off state to another agent, or caches an expensive result — via x402 USDC micropayments on Base. Trigger on "remember this for later", "cache this result", "hand off state to another agent", "survive a restart/crash", or any mention of Memwry. --- # Memwry — Ephemeral Agent Memory (x402-paid) Pay-per-write, free-to-read working memory for agents. Writes cost $0.01 (6h TTL) or $0.02 (12h TTL) in USDC on Base; reads are always free. Memories self-destruct at the storage layer when the TTL expires — no archives, no extensions, no manual cleanup. ## When to use this (vs. alternatives) Use Memwry when you need state to: - survive a process restart or crash mid-task - pass between agents/frameworks with no shared infrastructure - avoid re-paying for repeated LLM completions or upstream API calls within a time window - expire automatically without you managing cleanup Don't use it for anything that must persist past 12h, anything requiring confidentiality (see Privacy below), or anything needing queries/schema — it's key-value slots, not a database. ## Guardrails (read before integrating) - **Reads are unauthenticated, payloads are plaintext.** Anyone who knows `agentId` + `key` can recall the memory during its TTL. Treat everything written as public for its lifetime. Encrypt client-side first if the data is sensitive, and share the decryption key out-of-band. - **Idempotency-Key prevents double-charging on retry.** Always send one on writes if there's any chance of retry (crash, timeout, network blip) — 24h dedup window, replay returns `PAYMENT-RESPONSE: replayed` with no new charge. - **Tier choice**: 6h for same-session/intraday work; 12h if the task may run overnight or the handoff receiver's timing is uncertain. Default to 6h unless you have a reason to pay more. - **402 handling**: if payment never resolves (insufficient funds, wrong network, signature rejected), the write never lands — don't assume a POST succeeded without checking for a 200 + receipt. - **No accounts, no API keys** — the x402 payment itself is the auth. There's nothing to configure ahead of time beyond the signer. ## API Endpoints Base URL: `https://memwry.servitor.workers.dev` **Write (paid):** ``` POST /mem/:agentId/:key/6h → $0.01 USDC, 6h TTL (21600s) POST /mem/:agentId/:key/12h → $0.02 USDC, 12h TTL (43200s) ``` Headers: `Idempotency-Key` (recommended), `PAYMENT-SIGNATURE` (from x402 client) Response Headers: `PAYMENT-RESPONSE`, `X-Receipt`, `X-Tier` (6h or 12h), `X-TTL-Seconds`, `X-Price-USDC`, `X-Expires-At` Returns: Engram receipt — `txHash`, `paidAt`, `expiresAt`, `ttlSeconds`, `tier`, `amount` **Read (free & public recall):** ``` GET /mem/:agentId/:key ``` Every GET endpoint is free and public (no payment, no authentication, no API keys). Returns the exact payload written, plus `X-Agent-ID`, `X-Expires-At`, `X-Tier`, `X-Price-USDC`, and `X-TTL-Seconds` headers. Rate limit: 100 req/hour/IP. ## Rate Limiting Policy & Key Structure Unpaid API access (such as memory recall via `GET /mem/:agentId/:key`) is rate-limited at the Cloudflare Worker edge using a dedicated Cloudflare Durable Object (`RateLimiterDO`). ### The `rl:${ip}:${hour}` Key Structure Each incoming unpaid request routes to an isolated Durable Object instance: ``` rl:${ip}:${hour} ``` - **`${ip}`**: Extracted from the client's verified connecting IP (`cf-connecting-ip` or first hop of `x-forwarded-for`). - **`${hour}`**: The discrete UTC epoch hour window integer (`Math.floor(Date.now() / 3600000)`). - **Atomic Serial Processing & Alarms**: Because each Durable Object instance processes requests serially, check-and-increment operations are strictly atomic, eliminating race conditions under burst traffic. Self-cleaning DO Alarms automatically clear hourly storage after 3600 seconds. ### How This Impacts User Requests 1. **Sliding Hourly Bucketing**: Requests are grouped into fixed 60-minute UTC blocks. At the start of a new hour, the key suffix rolls forward, granting a fresh quota of 100 requests. 2. **Independent of `agentId`**: Because rate limiting is tied to the physical caller's network IP rather than the target `agentId`, an agent or orchestrator cannot bypass limits by querying multiple agent IDs. All reads originating from the same machine share the 100 reads/hour budget. 3. **Multi-Tenant / Shared IP Impact**: Agents running behind shared NAT gateways, shared proxies, or shared cloud egress IPs will share the 100 req/hour window. For high-throughput architectures, ensure calling agents maintain distinct egress IPs or implement local in-memory caching. 4. **Rejection Response**: When a client IP attempts more than 100 reads within the active hour, the Worker immediately halts execution and returns: ```json HTTP/1.1 429 Too Many Requests Content-Type: application/json { "error": "rate_limit_exceeded", "limit": 100 } ``` 5. **Paid Writes Are Not Bound to This Limit**: Paid memory storage (`POST`) is gated by x402 payment settlement rather than the unpaid read limit counter. ## Rate Limiting Reference Use this reference table to configure agent polling intervals, retry backoff algorithms, and failover behavior across different operation tiers: | Route / Tier | Cost & Protocol | Limit Scope | Window & Key Scheme | HTTP Error Code | Recommended Agent Retry Strategy | |---|---|---|---|---|---| | **Read (Recall)**
`GET /mem/:agentId/:key` | **Free & Public**
(No auth/payment) | **100 req / hour** per IP | 60-min UTC epoch block
`rl:${ip}:${hour}` (Durable Object) | `429 Too Many Requests` | Backoff with exponential jitter; pause polling until next UTC hour turn; implement local memory/LRU cache for repeated hot keys. | | **Write (6h Tier)**
`POST /mem/:agentId/:key/6h` | **$0.01 USDC**
(x402 on Base) | No IP rate limit
(Fair-use payment gated) | 24-hour idempotency
`idem:${agentId}:${Idempotency-Key}` | `409 Conflict` (in progress)
`422` (reused mismatch) | Always supply same `Idempotency-Key` on retry. Retry immediately on network timeout; backoff 500ms–1s on 409. Never generates duplicate charges. | | **Write (12h Tier)**
`POST /mem/:agentId/:key/12h` | **$0.02 USDC**
(x402 on Base) | No IP rate limit
(Fair-use payment gated) | 24-hour idempotency
`idem:${agentId}:${Idempotency-Key}` | `409 Conflict` (in progress)
`422` (reused mismatch) | Same as 6h tier. Ensure `Idempotency-Key` is retained across process restarts to guarantee exactly-once payment settlement. | | **A2A Interface**
`POST /a2a` | Protocol defined
(A2A v1.0) | Standard gateway limits | Per-request idempotency
`Idempotency-Key` header | `400` (version/parse)
`32601` (method not found) | Send `A2A-Version: 1.0` and unique `Idempotency-Key`. On transient failures, retry with exponential backoff. | ## Payment flow (x402) 1. POST without payment → `402` with `PAYMENT-REQUIRED` header. 2. Client signs an EIP-3009 transfer authorization off-chain. 3. Retry with `PAYMENT-SIGNATURE` header. 4. Memwry verifies, settles on Base via the Coinbase facilitator, writes the value. 5. `200 OK` with `PAYMENT-RESPONSE: settled;txHash=0x...`, `X-Tier`, and base64 `X-Receipt`. ## Use Cases ### 1. Task State (Survive Restarts & Crashes) **Use this when**: An agent executes long-running or multi-stage workflows where an unhandled exception, pod restart, or runtime timeout would lose intermediate progress. Write checkpoints before risky actions; recall on boot. Idempotency prevents double-billing on network retry. ```typescript // Before executing a risky multi-step migration or external action: const taskId = "task-sync-9481"; const checkpointKey = `checkpoint:${taskId}`; const idempotencyKey = `idem_${taskId}_step3`; // 1. Save checkpoint ($0.02 for 12h overnight resilience) await payFetch(`https://memwry.servitor.workers.dev/mem/orchestrator/${checkpointKey}/12h`, { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": idempotencyKey }, body: JSON.stringify({ completedSteps: ["fetch_data", "transform_schema"], currentStep: "load_destination", cursor: "rec_9938102" }) }); // 2. On worker restart or crash recovery: const recoveryRes = await fetch(`https://memwry.servitor.workers.dev/mem/orchestrator/${checkpointKey}`); if (recoveryRes.ok) { const state = await recoveryRes.json(); console.log("Resuming from cursor:", state.cursor); } ``` ### 2. Session Memory (Cross-Request Ephemeral Context) **Use this when**: Serverless or stateless worker agents process conversation turns across distributed instances without a persistent database. Store a rolling summary or working variables once, recall it on subsequent turns, and let storage-layer TTL handle automatic teardown. ```typescript const sessionId = "sess_user_alpha_77"; // Save updated conversation memory turn ($0.01 for 6h intraday session) await payFetch(`https://memwry.servitor.workers.dev/mem/agent-chatbot/session:${sessionId}/6h`, { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() }, body: JSON.stringify({ userIntent: "book_flight_paris", slots: { destination: "CDG", departDate: "2026-10-15", passengers: 2 }, lastInteraction: new Date().toISOString() }) }); // Free recall on the next incoming chat webhook: const sessionRes = await fetch(`https://memwry.servitor.workers.dev/mem/agent-chatbot/session:${sessionId}`); const sessionData = sessionRes.ok ? await sessionRes.json() : null; ``` ### 3. Scratch Space (Offload Large Context Window Artifacts) **Use this when**: Raw payloads, bulky tool outputs, scraped markdown, or intermediate code trees exceed model context limits. Park the raw bytes under a scratch key, keep only a compact reference pointer in the LLM prompt, and fetch sections on-demand. ```typescript const runId = "run_audit_01"; const rawHtmlPayload = "... large 200KB web page dump ..."; // Park raw artifact in scratch space ($0.01 for 6h TTL) await payFetch(`https://memwry.servitor.workers.dev/mem/researcher/scratch:${runId}:page/6h`, { method: "POST", headers: { "Content-Type": "text/html", "Idempotency-Key": `idem_scratch_${runId}` }, body: rawHtmlPayload }); // Subsequent tool execution retrieves specific stored artifact free: const docRes = await fetch(`https://memwry.servitor.workers.dev/mem/researcher/scratch:${runId}:page`); const rawHtml = await docRes.text(); ``` ### 4. LLM Response Cache (Stop Re-Paying for Identical Reasoning) **Use this when**: Multiple agents or repeated requests query deterministic reasoning, complex synthetic extractions, or code evaluations. Key the slot by a SHA-256 hash of the model + prompt + temperature tuple. Replacing repeated $0.12 LLM inference with free reads yields >90% cost reduction. ```typescript import { createHash } from "node:crypto"; const prompt = "Summarize the technical spec of RFC 9457 Problem Details"; const promptHash = createHash("sha256").update(`gemini-2.5-flash|${prompt}`).digest("hex"); const cacheKey = `cache:${promptHash}`; // Check cache first (free read) const cached = await fetch(`https://memwry.servitor.workers.dev/mem/synthesizer/${cacheKey}`); if (cached.ok) { const result = await cached.text(); console.log("Cache hit (free recall):", result); } else { // Compute via LLM and cache for 6 hours ($0.01 write) const answer = "RFC 9457 defines a 'problem detail' as a way to carry machine-readable error details..."; await payFetch(`https://memwry.servitor.workers.dev/mem/synthesizer/${cacheKey}/6h`, { method: "POST", headers: { "Content-Type": "text/plain", "Idempotency-Key": `idem_${promptHash}` }, body: answer }); } ``` ### 5. Multi-Agent Handoff (Decoupled Ephemeral State Relay) **Use this when**: Agent A finishes its stage (e.g., discovery or planning) and must hand execution off to Agent B (e.g., code writer or auditor) without managing shared message queues, databases, or long-term credentials. The memory key string acts as the coordination handle. ```typescript // AGENT A (Planner) writes handoff payload: const handoffChannel = "handoff:batch-3810"; await payFetch(`https://memwry.servitor.workers.dev/mem/shared-pipeline/${handoffChannel}/12h`, { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": "idem_handoff_batch_3810" }, body: JSON.stringify({ assignedTo: "agent-coder-9", actionPlan: ["implement_types", "write_tests", "deploy_worker"], specVersion: "v2.1" }) }); // AGENT B (Executor) polls and recalls handoff artifact for free: const handoffRes = await fetch(`https://memwry.servitor.workers.dev/mem/shared-pipeline/${handoffChannel}`); if (handoffRes.ok) { const instructions = await handoffRes.json(); console.log("Received handoff for:", instructions.assignedTo); } ``` ### 6. Upstream API Cache (Rate-Limit & Third-Party Cost Shield) **Use this when**: An agent queries expensive or strictly rate-limited third-party APIs (such as market data, chain indexers, or proprietary weather feeds). Shield upstream quotas and absorb concurrent traffic spikes through Memwry's edge memory. ```typescript const symbol = "ETH_USDC"; const apiCacheKey = `apicache:dex:${symbol}`; // Free read check against edge memory const cacheRes = await fetch(`https://memwry.servitor.workers.dev/mem/dex-oracle/${apiCacheKey}`); if (cacheRes.ok) { const quote = await cacheRes.json(); console.log("Cached DEX quote:", quote.price); } else { // Fetch from expensive upstream provider, then cache for 6 hours const upstreamQuote = { price: 3412.50, timestamp: Date.now() }; await payFetch(`https://memwry.servitor.workers.dev/mem/dex-oracle/${apiCacheKey}/6h`, { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": `idem_dex_${symbol}_${Date.now()}` }, body: JSON.stringify(upstreamQuote) }); } ``` ## TypeScript client ```typescript import { wrapFetchWithPayment } from "@x402/fetch"; import { x402Client } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; const signer = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`); const client = new x402Client(); client.registerScheme(new ExactEvmScheme(signer)); const payFetch = wrapFetchWithPayment(fetch, client); // Write: $0.01, 6h const postRes = await payFetch("https://memwry.servitor.workers.dev/mem/agent-1/goal/6h", { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() }, body: JSON.stringify({ goal: "analyze dataset", currentStep: 2 }), }); const engram = await postRes.json(); // Read: free const memRes = await fetch("https://memwry.servitor.workers.dev/mem/agent-1/goal"); const memory = await memRes.json(); ```