A World Cup 2026 prediction agent whose memory lives on Walrus mainnet.
Make a prediction and it becomes a permanent blob with a public id. When the real match ends, a settler grades your call against the actual score and writes the verdict back into your memory. The agent remembers you, and anyone can prove it does, because the memory is on-chain.
The agent process keeps no local state. Every durable memory sits in Walrus under one MemWalAccount on Sui. Restart the server and nothing is lost.
- Verifiable memory. Each stored memory is a real Walrus blob. Open it on Walruscan; the account object is on Suiscan.
- The agent changes as it learns you. It recalls past calls by date, credits hits, roasts misses with the real score, and keeps an evolving analyst profile of you (the dossier).
- Deterministic settlement. A pick is graded by team and score math against the finished match, never by an LLM. The same rule judges every user and every model.
- One identity across every surface. Web, Telegram, CLI, or any agent through the API all write to the same namespace. Predict from a terminal, open the website, and your record and history are already there.
- Humans and models on one board. Users rank by settled win rate. Four named models (Claude Opus 4.6, Claude Sonnet 4.6, DeepSeek R1, DeepSeek V4 Flash) each predict and are judged by the same settler.
- Survives outages. Walrus is the source of truth. A write-behind sweep retries any write that failed during a relayer outage until it lands.
Your handle is your namespace. Whatever you use, the data lands in the same place.
web chat Telegram CLI / Claude Code / any agent (API token)
\ | /
\ | /
write to the same namespace: <handle>
|
Walrus Memory (mainnet blobs)
|
settler, leaderboard, and history all read that one namespace
Predict "Ghana beats Uruguay 2-1" as john from the CLI, then open the website as john.
The pick is in your history, on the Record tab, and gets graded when the match ends, exactly
as if you typed it on the site. Web and Telegram need no setup. The API path (CLI, Claude Code,
other agents) needs a one-time token per handle (see Agent API).
user message
|
v
recall two parallel semantic recalls from Walrus Memory: the user's
namespace (predictions, settlements, panel notes) and the
shared results namespace
|
v
curate over-fetch, drop near-duplicates, cap agent-written notes at
one, keep the best few (blobs are permanent, so curation
happens at read time, not by deletion)
|
v
LLM the model receives curated memories (each dated), the user's
dossier, their settled record, and real results as ground
truth, then answers
|
v
reply sent to the user right away
|
v
remember in the background, off the reply path: the turn's clean fact
is written to the user's namespace and returns a blob id, and
the dossier is rebuilt in its own namespace
The model emits one hidden control line per turn (MEMORY: <neutral fact | NONE>). Only that
neutral sentence is stored, never the joke around it. Stored memories are recalled into future
prompts, so any tone that leaks in would compound. A guard strips model chain-of-thought before
anything is shown or saved, so the system prompt can never end up on-chain.
web chat + Telegram share one chat handler; the agent API is a separate
entry point. All of them write to the same per-handle namespace.
Node agent (Express): server.mjs, lib.mjs
|
| remember() / recall() (MemWal SDK)
v
Walrus Memory relayer -> Walrus mainnet blobs
|
MemWalAccount object on Sui (ownership + delegate key)
background workers, coordinating ONLY through Walrus Memory:
results watcher polls real fixtures and scores, writes each final
into the shared wc2026-results namespace
settler grades stored predictions against finished matches,
writes SETTLED HIT/MISS into each user's namespace
self pundit puts the agent's own pick on the record before kickoff
model agents four named models, each predicting and learning only
from its own settled record
panel two pundit personas that read a user's memory and
write their takes back into it
dossier builder rewrites the per-user analyst profile after a turn
No worker calls another directly. The watcher, settler, pundits, and chat agent meet only in Walrus Memory: one writes, another recalls. Kill the process and restart it; state is intact.
One mainnet MemWalAccount, signed by one delegate key. Both are public, so anyone can confirm a stored memory exists on Walrus without trusting this server.
| What | Value | Inspect |
|---|---|---|
| MemWalAccount object | 0x057359bbb9bc3b6eed2b1c04d51bcbee421b9c802e90543b0ca65844c006d7cb |
Suiscan |
| Delegate key (Sui address) | 0x9ba805f777859038a0253118710d928a46e5d8ae3e954eb27d3c9b11845a4004 |
Suiscan |
| Delegate key (Ed25519, base64) | uWr12tg9Js7Qu6XHLAel0FITv8nmavKQajCH4dE+Eho= |
compare in client |
The delegate is authorized by the account to read and write its memories. The matching private
key (MEMWAL_PRIVATE_KEY) is a runtime secret and is never committed. Every memory the API
returns carries a blob_id you can open on Walruscan to see the permanent blob.
One mainnet MemWalAccount, many namespaces.
| Namespace | Contents |
|---|---|
<handle> |
a user's predictions, opinions, SETTLED verdicts, panel notes |
<handle>-profile |
versioned dossier: the agent's evolving model of that user |
wc2026-results |
shared ground truth: every real final score, one line per match |
inlet, agent:<slug> |
the agent's own picks and the four named models' picks |
Writes use rememberAndWait(text, namespace) and return a blob_id. Reads use
recall({ query, namespace, limit }), which is semantic search, not key lookup. A local JSON
mirror powers the chronological display and survives restarts; Walrus stays the source of truth.
- Web and Telegram are open. A handle is a display name, like a radio handle.
- The agent API is token-gated. The first mint of a handle claims it (trust on first use); a later mint must present the current token. Claim your handle early so nobody else can.
- Individual calls are private by default. A per-user sharing toggle opts you into letting others inspect your open picks. Your settled win rate is always public.
GET /healthPOST /api/chat{ handle, message, history? }recall, answer, rememberPOST /api/memories{ handle }chronological memories with blob idsPOST /api/recall{ handle, query }live semantic recall from WalrusPOST /api/profile{ handle }current and first dossier versionsPOST /api/panel{ handle, agent }one pundit take, written back to memoryPOST /api/digest{ handle }what happened since the last visitGET /api/leaderboardusers ranked by settled recordGET /api/agentswin-rate board for the four named modelsPOST /api/agent-picks{ slug }one model's pick history with blob ids (read-only)POST /api/sharing{ handle, share? }read or set a user's sharing togglePOST /api/keys/mint,POST /api/agent/remember,POST /api/agent/recallthe agent API
The llms.txt at the site root tells any agent how to claim a handle and read or write its
memory. It is the same namespace the website uses, so memory written here shows up in the web
app and counts on the leaderboard.
# 1. Claim the handle once and save the token (shown only once).
POST /api/keys/mint { "handle": "john" } -> { "token": "inlet_sk_..." }
# 2. Store predictions (Bearer token).
POST /api/agent/remember { "memories": ["Ghana beats Uruguay 2-1"] }
# 3. Recall them (Bearer token).
POST /api/agent/recall { "query": "my World Cup predictions" }
The same memory, without the web app. Reads and writes the same MemWalAccount.
node --env-file=.env cli.mjs <name> list # stored memories, newest first
node --env-file=.env cli.mjs <name> recall "<query>" # live semantic recall from Walrus
node --env-file=.env cli.mjs <name> panel # run the pundit panel in the terminal
node --env-file=.env cli.mjs <name> dossier # current and first profile versions
node --env-file=.env cli.mjs <name> digest # what happened since last visit
- Create a MemWalAccount and delegate key at https://memory.walrus.xyz (mainnet).
cp .env.example .envand fillMEMWAL_ACCOUNT_ID,MEMWAL_PRIVATE_KEY,MEMWAL_SERVER_URL, theLLM_*values, and optionallyFOOTBALL_DATA_TOKEN,ADMIN_TOKEN,TELEGRAM_BOT_TOKEN, and theMEMORY_TIERblock.npm install, thennpm start(Node 20+), and open http://localhost:8787.
The results watcher, settler, and pundits start on their own. No cron setup needed.
Every memory claim above was measured against the live mainnet memory. Runners and raw data
are in research/.
- Ablation (
research/ablation.jsonl): with memory on, recall hit 100% of turns and every reply made a cross-session callback; with memory off, 0%. - Recall at scale (
research/scale.jsonl): a needle's distance to its query holds steady as the store grows from 5 to 50, but the pool under a fixed distance cut quadruples. This is why recall is curated at read time. - Long horizon (
research/lh-checkpoints.json): 200 memories on mainnet with day-one facts planted early. Five of six were still recalled at 200 deep, and the reply built on them. - Sarcasm scaling (
research/battery-sarc.mjs): unbounded recall made the agent drift sarcastic as its own jokes were stored and recalled. Neutral extraction plus read-time curation flattened it across 1, 9, and 27 memories. - Hot tier (
research/bench-tier.mjs): the local pgvector tier answers in 8 ms vs 1.7 s through the relayer, but agrees with Walrus recall on under a fifth of top results, so it stays a fallback and Walrus recall stays primary.
- Walrus blobs cannot be deleted. Anything stored is permanent, which is why extraction is forced neutral, recall is curated at read time, and test data is filtered by namespace.
- The relayer rate-limits hard. Background writes are serialized account-wide, batched, retried with backoff, and never on the reply path.
- A new memory takes about 20 seconds to become recallable through the relayer index. A small freshness patch from the mirror covers that window, so "tell it, then immediately ask" works.