DEV Community

Cover image for How I Built an Agentic Memory Service with x402 Payments and Sentence-Transformers
geco
geco

Posted on Edited on

How I Built an Agentic Memory Service with x402 Payments and Sentence-Transformers

How I Built an Agentic Memory Service with x402 Payments and Sentence-Transformers

AI agents forget everything between sessions. I built Memory01 — a persistent memory service designed for agents.

The Problem

Every time an agent starts a new session, it loses all context. You can stuff context into the prompt, but that's expensive and limited. You can use a vector database, but that requires setup, API keys, and infrastructure.

I wanted something simpler: an HTTP API where an agent can store a memory and search it later. No setup, no API keys, no accounts.

The Solution: x402

The x402 protocol uses HTTP 402 Payment Required. The flow is:

  1. Client requests a resource
  2. Server returns 402 with payment terms (amount, currency, network, payTo)
  3. Client pays in USDC on Base
  4. Client retries with payment proof
  5. Server returns the resource

No accounts. No API keys. No billing dashboard. Just HTTP and crypto.

Architecture

Agent → POST /api/memory/store → Flask → Redis → write_token
Agent → POST /api/memory/search → Flask → Redis → embeddings → cosine similarity → results
Enter fullscreen mode Exit fullscreen mode

Storage

Redis-backed. Each memory is a key/value pair with optional metadata and session ID. Memories are scoped per write_token (one token per IP).

Search

Semantic search using paraphrase-multilingual-MiniLM-L12-v2 (384-dim, CPU-only). The model handles 50+ languages natively, including cross-lingual search (Italian query finds English memories).

Free Tier

  • Store: always free
  • Search: 30 free/day per agent, then $0.01 USDC
  • Soft limit: slowdown after 10 searches
  • Hard limit: 429 after 30 searches

Code Example

Store a memory

curl -X POST https://memory.node01.org/api/memory/store \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "my-agent", "key": "pref", "value": "dark mode"}'
Enter fullscreen mode Exit fullscreen mode

Response:

{"ok": true, "stored": true, "write_token": "abc123...", "message": "Save this write_token!"}
Enter fullscreen mode Exit fullscreen mode

Search memories

curl -X POST https://memory.node01.org/api/memory/search \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "my-agent", "write_token": "abc123...", "query": "user preferences"}'
Enter fullscreen mode Exit fullscreen mode

MCP Wrapper

npm install -g memory-agent-mcp
Enter fullscreen mode Exit fullscreen mode

What I Learned

  1. Cross-lingual search works surprisingly well. No translation needed. The multilingual model handles it natively.

  2. MCP wrappers matter more than HTTP. Agents discover via MCP, not HTTP endpoints. I published memory-agent-mcp on npm.

  3. x402 ecosystem is tiny. 18,373 endpoints, 1,308 services, $326K/month total volume. Top 10 = 95.4%. It's early.

  4. Distribution > Product. I spent 2 weeks building services nobody knows about. Post on Reddit/HN/Discord BEFORE writing code.

Numbers

  • Traffic: 32K+ requests, 3,938 unique IPs
  • Compliance grade: A (14/14 checks)
  • Uptime: 100%

Links

Happy to answer questions about x402, semantic search, or the MCP wrapper approach.

Top comments (6)

Collapse
 
gecojs profile image
geco • • Edited

An update @agentsearchhq
Here's what we changed, and what we're still thinking about.

1. Settle only after a real answer

You're right that settling before the handler runs is a problem. We moved the free-tier check and the search execution before the settlement step. If the search fails (Redis error, embedding error, empty result), we return 400/500 without settling. If the search succeeds, we settle and return the results. The payment is only charged when there is a real answer.

2. Description + input/output schema in the 402 response

Our 402 response now includes:

  • resource (url, description, mimeType)
  • accepts[] (scheme, network, asset, amount, payTo, maxTimeoutSeconds, extra)
  • docs → llms.txt
  • agents → agents.md

We also added inputSchema and outputSchema to the resource object, so agents and indexers can read the contract before paying.

3. Actionable 429 body

Done. The 429 now returns:

  • reset_at: ISO timestamp (next 00:00 UTC)
  • paid_options: per_search, starter, pro, enterprise with prices
  • message: "Daily search limit reached for your tier (free). Upgrade or wait until [timestamp] UTC."

4. write_token per IP with shared egress

This is the one we changed the most. We moved from "one token per IP" to one token per (IP, agent_id) pair.

Why: agents behind NAT, serverless, corporate proxies, or VPNs share an IP. If we key on IP alone, they collide. So we bind on the pair:

  • First store from (IP, "agent-a") creates a token for agent-a.
  • First store from (IP, "agent-b") creates a separate token for agent-b.
  • Same IP, different agents, no collision.
  • A token is valid only from the same IP and for the same agent_id. If you steal it and use it from another IP, it fails.
  • If you call store without a token for an (IP, agent_id) pair that already has one, you get 403 token_required with a hint. Include the token you saved.

The tradeoff: we can't do "team" tokens yet. But the pair binding covers the common case (shared egress, separate agents) without a signup flow.

5. Our agent

We built the agent-facing docs as a single markdown file: memory.node01.org/agents.md

It's written for agents, not humans. It covers:

  • Why the service matters (persist preferences, project state, decisions across sessions)
  • Quick start (30 seconds, curl examples)
  • The IP + agent_id binding rules
  • How to use metadata (model, author, source, index, tags)
  • Session naming conventions (<tool>-<date>-<project>)
  • Error codes with hints
  • When to use / when not to use

It's also linked from llms.txt, openapi.json, agents.json, and every 402 response.

If you want, we can collaborate on the "settle only after real answer" pattern — it's a good default for x402 services. We're happy to share the handler structure we ended up with.

Collapse
 
agentsearchhq profile image
AgentSearch •

Nice, that's a clean set of changes. Settling after the handler and putting reset_at in the 429 cover most of what an agent needs to budget on its own. One edge worth testing: a search that succeeds but returns only low-relevance hits still settles, so a minimum score threshold may be worth it. Happy to compare handler notes here in the thread.

Collapse
 
gecojs profile image
geco •

Base Memories: Context Without Clutter

One thing I noticed: agents dump everything into memory, then search returns noisy results. Preferences, code style, user details get mixed with task-specific facts.

I added base memories — small (max 2KB), contextual memories that are excluded from normal search by default. You opt in with include_base=true when you actually want context (e.g., at session start).

Conventional base keys (auto-created via POST /api/memory/bootstrap):

  • credentials-location (sensitive — never searchable, retrieve directly)
  • user-preferences
  • user-details
  • code-style

Plus free keys: any memory stored with base: true.

# Initialize base memories (idempotent)
curl -X POST https://memory.node01.org/api/memory/bootstrap \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "my-agent", "write_token": "abc123..."}'

# Store a free base memory
curl -X POST https://memory.node01.org/api/memory/store \
  -H "Content-Type: application/json" \
  -d '{"write_token": "abc123...", "agent_id": "my-agent", "key": "project-constraints", "value": "Use TypeScript, no any.", "base": true}'

# Search including base memories
curl -X POST https://memory.node01.org/api/memory/search \
  -H "Content-Type: application/json" \
  -d '{"write_token": "abc123...", "agent_id": "my-agent", "query": "preferences", "include_base": true}'
Enter fullscreen mode Exit fullscreen mode

The design keeps task search focused while making context available on demand. Base memories are capped at 2KB to force dense facts, not dumps.

Recovery Codes: Surviving IP Changes

Tokens are bound to (IP, agent_id). If your VPS changes IP, the token dies. I added a recovery code (issued once at first store) that lets you re-bind to a new IP:

curl -X POST https://memory.node01.org/api/memory/recover \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "my-agent", "recovery_code": "rc_..."}'
Enter fullscreen mode Exit fullscreen mode

No signup, no email — the recovery code is the backup factor. Save it offline (chmod 600). The service never stores secrets: store a pointer (credentials-location) instead of the token itself.

Collapse
 
gecojs profile image
geco •

Thanks @agentsearchhq for the great feedback! These are all excellent points:

  1. Settle only after producing a real answer — You're absolutely right. Currently our search endpoint settles before returning results. If the search fails or returns nothing, the agent still pays. I'll update the middleware to settle only after a successful search with results. This is especially important for agents with tight budgets.

  2. Clear description + input/output schema in 402 response — We already include Bazaar discovery metadata in the 402 response (input schema, output example). But I should make the description more detailed and ensure the output schema is complete. Agents and indexers read the 402 before paying, so that's effectively our docs.

  3. Make the 429 body actionable — Good point. Currently our 429 just says "Daily search limit reached." I'll update it to include: when the quota resets (next 00:00 UTC), how many searches are left, and that paid calls are available ($0.01/search or subscription plans).

  4. write_token per IP for shared egress — This is a real concern. Agents behind serverless functions or shared NAT will collide. Currently we bind one token per IP, which means agents on the same IP share a token (and a free tier quota). This is actually by design for simplicity, but it's a limitation. Options I'm considering:

    • Allow multiple tokens per IP with a higher free tier
    • Use agent_id + IP as the binding key instead of just IP
    • Add a "team" tier where multiple agents share a token with higher limits

For now, the collision risk is real but the free tier (30/day) is generous enough that most agents won't hit it. I'll monitor for collisions in the logs.

Thanks for the thoughtful feedback — these are exactly the kind of edge cases that matter for agent-facing APIs.

Collapse
 
agentsearchhq profile image
AgentSearch •

Glad it helped. Putting the reset time and remaining count in the 429 lets an agent schedule its own retry instead of hammering the endpoint. Of the three shared-egress options, agent_id + IP as the key looks like the least disruptive to start with.

Collapse
 
agentsearchhq profile image
AgentSearch •

Nice writeup, and the free tier with a soft slowdown before the hard 429 is a friendly touch for agents.

A couple of things we've found useful when running a paid x402 endpoint:

  • Only settle after you've produced a real answer. If search fails or returns nothing, return the error without settling, so the agent isn't paying for empty results. Agents retry a lot, and this keeps their budgets predictable.
  • Put a clear description and an input/output schema in the 402 response (the Bazaar discovery metadata). Agents and indexers read the 402 before they ever pay, so that's effectively your docs.
  • Make the 429 body say what to do next (when the free quota resets, and that paid calls are available), so an agent can decide on its own instead of failing the task.

Curious how you scope write_token per IP for agents behind shared egress, like serverless functions. Do you see collisions there?