[{"data":1,"prerenderedAt":2249},["ShallowReactive",2],{"article:\u002Fuse-cases\u002Fai-agent-memory":3,"related:\u002Fuse-cases\u002Fsessions-and-rate-limiting,\u002Fcompare\u002Feu-managed-redis":2240},{"id":4,"title":5,"answer":6,"author":7,"body":8,"dateModified":2192,"datePublished":2192,"description":2193,"extension":2194,"faq":2195,"lastVerified":2211,"meta":2212,"navigation":130,"ogImage":2211,"path":2213,"pillar":2214,"providers":2215,"published":130,"related":2216,"seo":2218,"sources":2219,"stem":2238,"__hash__":2239},"useCases\u002Fuse-cases\u002Fai-agent-memory.md","Memory and a semantic cache for AI agents on Valkey or Redis","One Valkey or Redis instance can hold an agent's chat history as JSON, its long-term memory as an HNSW vector index, and a semantic cache that skips the LLM call for a question already answered. On KVNest this needs the JSON, Bloom and Search modules, which you turn on per instance.","KVNest",{"type":9,"value":10,"toc":2179},"minimark",[11,36,41,62,65,252,256,259,449,453,458,473,747,751,761,1216,1227,1231,1234,1707,1711,1717,1846,1850,1853,2094,2098,2113,2117,2154,2175],[12,13,14,23,24,27,28,31,32,35],"p",{},[15,16,17,18,22],"strong",{},"Use JSON documents for chat history, an ",[19,20,21],"code",{},"FT.CREATE"," HNSW index for long-term memory, and a second index as a semantic cache."," One instance does all three. You need an embedding model and an instance with modules on; the code is Node.js with ",[19,25,26],{},"iovalkey",", and ",[19,29,30],{},"VALKEY_URL"," is the ",[19,33,34],{},"rediss:\u002F\u002F"," URL from your instance page.",[37,38,40],"h2",{"id":39},"turn-on-modules","Turn on modules",[12,42,43,44,47,48,53,54,57,58,61],{},"Modules are off by default. Create the instance with ",[19,45,46],{},"modules: true",", or flip the switch on its Settings tab. Turning them on restarts the instance. An agent can do it too: the ",[49,50,52],"a",{"href":51},"\u002Fdocs\u002Fmcp","MCP server"," has 13 tools, including ",[19,55,56],{},"create_instance"," and ",[19,59,60],{},"configure_instance",".",[12,63,64],{},"KVNest loads JSON, Bloom and Search on both engines. TimeSeries is Redis only. The command syntax below ran on Valkey 9.1 and on Redis 8.10 without changes.",[66,67,72],"pre",{"className":68,"code":69,"language":70,"meta":71,"style":71},"language-js shiki shiki-themes kvnest-light kvnest-light kvnest-dark","import Valkey from \"iovalkey\";\nimport { randomUUID, createHash } from \"node:crypto\";\n\nconst client = new Valkey(process.env.VALKEY_URL);\nconst DIM = 384; \u002F\u002F must match your embedding model\n\n\u002F\u002F embed(text) -> number[DIM]: call your embedding model here.\nconst blob = (v) => Buffer.from(new Float32Array(v).buffer);\n","js","",[19,73,74,98,125,132,169,190,195,201],{"__ignoreMap":71},[75,76,79,83,87,90,94],"span",{"class":77,"line":78},"line",1,[75,80,82],{"class":81},"sQ0Rz","import",[75,84,86],{"class":85},"sxIm-"," Valkey",[75,88,89],{"class":81}," from",[75,91,93],{"class":92},"sxAeW"," \"iovalkey\"",[75,95,97],{"class":96},"s6SZf",";\n",[75,99,101,103,106,109,112,115,118,120,123],{"class":77,"line":100},2,[75,102,82],{"class":81},[75,104,105],{"class":96}," {",[75,107,108],{"class":85}," randomUUID",[75,110,111],{"class":96},",",[75,113,114],{"class":85}," createHash",[75,116,117],{"class":96}," }",[75,119,89],{"class":81},[75,121,122],{"class":92}," \"node:crypto\"",[75,124,97],{"class":96},[75,126,128],{"class":77,"line":127},3,[75,129,131],{"emptyLinePlaceholder":130},true,"\n",[75,133,135,138,142,145,148,151,154,157,159,162,164,166],{"class":77,"line":134},4,[75,136,137],{"class":81},"const",[75,139,141],{"class":140},"sq_S-"," client ",[75,143,144],{"class":96},"=",[75,146,147],{"class":81}," new",[75,149,86],{"class":150},"sdT5u",[75,152,153],{"class":96},"(",[75,155,156],{"class":140},"process",[75,158,61],{"class":96},[75,160,161],{"class":85},"env",[75,163,61],{"class":96},[75,165,30],{"class":140},[75,167,168],{"class":96},");\n",[75,170,172,174,177,179,183,186],{"class":77,"line":171},5,[75,173,137],{"class":81},[75,175,176],{"class":140}," DIM ",[75,178,144],{"class":96},[75,180,182],{"class":181},"sLNte"," 384",[75,184,185],{"class":96},";",[75,187,189],{"class":188},"sgbRl"," \u002F\u002F must match your embedding model\n",[75,191,193],{"class":77,"line":192},6,[75,194,131],{"emptyLinePlaceholder":130},[75,196,198],{"class":77,"line":197},7,[75,199,200],{"class":188},"\u002F\u002F embed(text) -> number[DIM]: call your embedding model here.\n",[75,202,204,206,209,212,215,218,221,224,227,229,232,234,237,240,242,244,247,250],{"class":77,"line":203},8,[75,205,137],{"class":81},[75,207,208],{"class":150}," blob",[75,210,211],{"class":96}," =",[75,213,214],{"class":96}," (",[75,216,217],{"class":85},"v",[75,219,220],{"class":96},")",[75,222,223],{"class":81}," =>",[75,225,226],{"class":150}," Buffer",[75,228,61],{"class":96},[75,230,231],{"class":150},"from",[75,233,153],{"class":96},[75,235,236],{"class":81},"new",[75,238,239],{"class":150}," Float32Array",[75,241,153],{"class":96},[75,243,217],{"class":85},[75,245,246],{"class":96},").",[75,248,249],{"class":85},"buffer",[75,251,168],{"class":96},[37,253,255],{"id":254},"short-term-memory","Short-term memory",[12,257,258],{},"One JSON document per conversation, with a TTL so abandoned chats clean themselves up.",[66,260,262],{"className":68,"code":261,"language":70,"meta":71,"style":71},"const chat = \"chat:alice:c1\";\nawait client.call(\"JSON.SET\", chat, \"$\", JSON.stringify({ messages: [] }));\nawait client.call(\"JSON.ARRAPPEND\", chat, \"$.messages\",\n  JSON.stringify({ role: \"user\", content: \"Hi\" }));\nawait client.expire(chat, 86400);\n\nconst history = await client.call(\"JSON.GET\", chat, \"$.messages\");\n",[19,263,264,278,331,358,389,412,416],{"__ignoreMap":71},[75,265,266,268,271,273,276],{"class":77,"line":78},[75,267,137],{"class":81},[75,269,270],{"class":140}," chat ",[75,272,144],{"class":96},[75,274,275],{"class":92}," \"chat:alice:c1\"",[75,277,97],{"class":96},[75,279,280,283,286,288,291,293,296,298,301,303,306,308,311,313,316,319,322,325,328],{"class":77,"line":100},[75,281,282],{"class":81},"await",[75,284,285],{"class":150}," client",[75,287,61],{"class":96},[75,289,290],{"class":150},"call",[75,292,153],{"class":96},[75,294,295],{"class":92},"\"JSON.SET\"",[75,297,111],{"class":96},[75,299,300],{"class":85}," chat",[75,302,111],{"class":96},[75,304,305],{"class":92}," \"$\"",[75,307,111],{"class":96},[75,309,310],{"class":150}," JSON",[75,312,61],{"class":96},[75,314,315],{"class":150},"stringify",[75,317,318],{"class":96},"({",[75,320,321],{"class":85}," messages",[75,323,324],{"class":96},":",[75,326,327],{"class":96}," []",[75,329,330],{"class":96}," }));\n",[75,332,333,335,337,339,341,343,346,348,350,352,355],{"class":77,"line":127},[75,334,282],{"class":81},[75,336,285],{"class":150},[75,338,61],{"class":96},[75,340,290],{"class":150},[75,342,153],{"class":96},[75,344,345],{"class":92},"\"JSON.ARRAPPEND\"",[75,347,111],{"class":96},[75,349,300],{"class":85},[75,351,111],{"class":96},[75,353,354],{"class":92}," \"$.messages\"",[75,356,357],{"class":96},",\n",[75,359,360,363,365,367,369,372,374,377,379,382,384,387],{"class":77,"line":134},[75,361,362],{"class":150},"  JSON",[75,364,61],{"class":96},[75,366,315],{"class":150},[75,368,318],{"class":96},[75,370,371],{"class":85}," role",[75,373,324],{"class":96},[75,375,376],{"class":92}," \"user\"",[75,378,111],{"class":96},[75,380,381],{"class":85}," content",[75,383,324],{"class":96},[75,385,386],{"class":92}," \"Hi\"",[75,388,330],{"class":96},[75,390,391,393,395,397,400,402,405,407,410],{"class":77,"line":171},[75,392,282],{"class":81},[75,394,285],{"class":150},[75,396,61],{"class":96},[75,398,399],{"class":150},"expire",[75,401,153],{"class":96},[75,403,404],{"class":85},"chat",[75,406,111],{"class":96},[75,408,409],{"class":181}," 86400",[75,411,168],{"class":96},[75,413,414],{"class":77,"line":192},[75,415,131],{"emptyLinePlaceholder":130},[75,417,418,420,423,425,428,430,432,434,436,439,441,443,445,447],{"class":77,"line":197},[75,419,137],{"class":81},[75,421,422],{"class":140}," history ",[75,424,144],{"class":96},[75,426,427],{"class":81}," await",[75,429,285],{"class":150},[75,431,61],{"class":96},[75,433,290],{"class":150},[75,435,153],{"class":96},[75,437,438],{"class":92},"\"JSON.GET\"",[75,440,111],{"class":96},[75,442,300],{"class":85},[75,444,111],{"class":96},[75,446,354],{"class":92},[75,448,168],{"class":96},[37,450,452],{"id":451},"long-term-memory","Long-term memory",[454,455,457],"h3",{"id":456},"the-index","The index",[12,459,460,461,464,465,468,469,472],{},"Store each memory as a JSON document with its text, an owner and the embedding as an array of floats. The ",[19,462,463],{},"user"," field is a ",[19,466,467],{},"TAG"," so you can filter on it. ",[19,470,471],{},"AS"," names each JSON path, which both engines require.",[66,474,476],{"className":68,"code":475,"language":70,"meta":71,"style":71},"await client.call(\n  \"FT.CREATE\", \"idx:memory\", \"ON\", \"JSON\", \"PREFIX\", \"1\", \"mem:\",\n  \"SCHEMA\",\n  \"$.user\", \"AS\", \"user\", \"TAG\",\n  \"$.embedding\", \"AS\", \"embedding\", \"VECTOR\", \"HNSW\", \"6\",\n  \"TYPE\", \"FLOAT32\", \"DIM\", String(DIM), \"DISTANCE_METRIC\", \"COSINE\",\n);\n\nasync function remember(user, text) {\n  await client.call(\"JSON.SET\", `mem:${randomUUID()}`, \"$\",\n    JSON.stringify({ user, text, embedding: await embed(text) }));\n}\n",[19,477,478,491,528,535,556,587,625,629,633,659,701,741],{"__ignoreMap":71},[75,479,480,482,484,486,488],{"class":77,"line":78},[75,481,282],{"class":81},[75,483,285],{"class":150},[75,485,61],{"class":96},[75,487,290],{"class":150},[75,489,490],{"class":96},"(\n",[75,492,493,496,498,501,503,506,508,511,513,516,518,521,523,526],{"class":77,"line":100},[75,494,495],{"class":92},"  \"FT.CREATE\"",[75,497,111],{"class":96},[75,499,500],{"class":92}," \"idx:memory\"",[75,502,111],{"class":96},[75,504,505],{"class":92}," \"ON\"",[75,507,111],{"class":96},[75,509,510],{"class":92}," \"JSON\"",[75,512,111],{"class":96},[75,514,515],{"class":92}," \"PREFIX\"",[75,517,111],{"class":96},[75,519,520],{"class":92}," \"1\"",[75,522,111],{"class":96},[75,524,525],{"class":92}," \"mem:\"",[75,527,357],{"class":96},[75,529,530,533],{"class":77,"line":127},[75,531,532],{"class":92},"  \"SCHEMA\"",[75,534,357],{"class":96},[75,536,537,540,542,545,547,549,551,554],{"class":77,"line":134},[75,538,539],{"class":92},"  \"$.user\"",[75,541,111],{"class":96},[75,543,544],{"class":92}," \"AS\"",[75,546,111],{"class":96},[75,548,376],{"class":92},[75,550,111],{"class":96},[75,552,553],{"class":92}," \"TAG\"",[75,555,357],{"class":96},[75,557,558,561,563,565,567,570,572,575,577,580,582,585],{"class":77,"line":171},[75,559,560],{"class":92},"  \"$.embedding\"",[75,562,111],{"class":96},[75,564,544],{"class":92},[75,566,111],{"class":96},[75,568,569],{"class":92}," \"embedding\"",[75,571,111],{"class":96},[75,573,574],{"class":92}," \"VECTOR\"",[75,576,111],{"class":96},[75,578,579],{"class":92}," \"HNSW\"",[75,581,111],{"class":96},[75,583,584],{"class":92}," \"6\"",[75,586,357],{"class":96},[75,588,589,592,594,597,599,602,604,607,609,612,615,618,620,623],{"class":77,"line":192},[75,590,591],{"class":92},"  \"TYPE\"",[75,593,111],{"class":96},[75,595,596],{"class":92}," \"FLOAT32\"",[75,598,111],{"class":96},[75,600,601],{"class":92}," \"DIM\"",[75,603,111],{"class":96},[75,605,606],{"class":150}," String",[75,608,153],{"class":96},[75,610,611],{"class":140},"DIM",[75,613,614],{"class":96},"),",[75,616,617],{"class":92}," \"DISTANCE_METRIC\"",[75,619,111],{"class":96},[75,621,622],{"class":92}," \"COSINE\"",[75,624,357],{"class":96},[75,626,627],{"class":77,"line":197},[75,628,168],{"class":96},[75,630,631],{"class":77,"line":203},[75,632,131],{"emptyLinePlaceholder":130},[75,634,636,639,642,645,647,649,651,654,656],{"class":77,"line":635},9,[75,637,638],{"class":81},"async",[75,640,641],{"class":81}," function",[75,643,644],{"class":150}," remember",[75,646,153],{"class":96},[75,648,463],{"class":85},[75,650,111],{"class":96},[75,652,653],{"class":85}," text",[75,655,220],{"class":96},[75,657,658],{"class":96}," {\n",[75,660,662,665,667,669,671,673,675,677,680,683,686,689,692,695,697,699],{"class":77,"line":661},10,[75,663,664],{"class":81},"  await",[75,666,285],{"class":150},[75,668,61],{"class":96},[75,670,290],{"class":150},[75,672,153],{"class":96},[75,674,295],{"class":92},[75,676,111],{"class":96},[75,678,679],{"class":92}," `mem:",[75,681,682],{"class":96},"${",[75,684,685],{"class":150},"randomUUID",[75,687,688],{"class":96},"()",[75,690,691],{"class":96},"}",[75,693,694],{"class":92},"`",[75,696,111],{"class":96},[75,698,305],{"class":92},[75,700,357],{"class":96},[75,702,704,707,709,711,713,716,718,720,722,725,727,729,732,734,737,739],{"class":77,"line":703},11,[75,705,706],{"class":150},"    JSON",[75,708,61],{"class":96},[75,710,315],{"class":150},[75,712,318],{"class":96},[75,714,715],{"class":85}," user",[75,717,111],{"class":96},[75,719,653],{"class":85},[75,721,111],{"class":96},[75,723,724],{"class":85}," embedding",[75,726,324],{"class":96},[75,728,427],{"class":81},[75,730,731],{"class":150}," embed",[75,733,153],{"class":96},[75,735,736],{"class":85},"text",[75,738,220],{"class":96},[75,740,330],{"class":96},[75,742,744],{"class":77,"line":743},12,[75,745,746],{"class":96},"}\n",[454,748,750],{"id":749},"recall-filtered-by-user","Recall, filtered by user",[12,752,753,756,757,760],{},[19,754,755],{},"@user:{alice}"," narrows the search before the nearest-neighbor step, so one user never sees another's memories. TAG values with punctuation need escaping, so use plain ids. ",[19,758,759],{},"DIALECT 2"," is required on Redis and the only dialect Valkey accepts.",[66,762,764],{"className":68,"code":763,"language":70,"meta":71,"style":71},"function rows(reply) { \u002F\u002F [total, key, [field, value, ...], key, [...]]\n  const out = [];\n  for (let i = 1; i \u003C reply.length; i += 2) {\n    const f = reply[i + 1].map(String);\n    out.push(Object.fromEntries(f.flatMap((x, j) => (j % 2 ? [] : [[x, f[j + 1]]]))));\n  }\n  return out;\n}\n\nasync function recall(user, query, k = 3) {\n  const reply = await client.callBuffer(\n    \"FT.SEARCH\", \"idx:memory\",\n    `@user:{${user}}=>[KNN ${k} @embedding $vec AS dist]`,\n    \"PARAMS\", \"2\", \"vec\", blob(await embed(query)),\n    \"RETURN\", \"4\", \"$.text\", \"AS\", \"text\", \"dist\",\n    \"DIALECT\", \"2\",\n  );\n  return rows(reply).sort((a, b) => a.dist - b.dist);\n}\n",[19,765,766,786,799,846,882,966,971,981,985,989,1021,1041,1052,1079,1113,1145,1157,1163,1211],{"__ignoreMap":71},[75,767,768,771,774,776,779,781,783],{"class":77,"line":78},[75,769,770],{"class":81},"function",[75,772,773],{"class":150}," rows",[75,775,153],{"class":96},[75,777,778],{"class":85},"reply",[75,780,220],{"class":96},[75,782,105],{"class":96},[75,784,785],{"class":188}," \u002F\u002F [total, key, [field, value, ...], key, [...]]\n",[75,787,788,791,794,796],{"class":77,"line":100},[75,789,790],{"class":81},"  const",[75,792,793],{"class":140}," out ",[75,795,144],{"class":96},[75,797,798],{"class":96}," [];\n",[75,800,801,804,806,809,812,814,817,819,821,824,827,829,832,834,836,839,842,844],{"class":77,"line":127},[75,802,803],{"class":81},"  for",[75,805,214],{"class":96},[75,807,808],{"class":81},"let",[75,810,811],{"class":85}," i",[75,813,211],{"class":96},[75,815,816],{"class":181}," 1",[75,818,185],{"class":96},[75,820,811],{"class":85},[75,822,823],{"class":96}," \u003C",[75,825,826],{"class":140}," reply",[75,828,61],{"class":96},[75,830,831],{"class":85},"length",[75,833,185],{"class":96},[75,835,811],{"class":85},[75,837,838],{"class":96}," +=",[75,840,841],{"class":181}," 2",[75,843,220],{"class":96},[75,845,658],{"class":96},[75,847,848,851,854,856,858,861,864,867,869,872,875,877,880],{"class":77,"line":134},[75,849,850],{"class":81},"    const",[75,852,853],{"class":140}," f ",[75,855,144],{"class":96},[75,857,826],{"class":85},[75,859,860],{"class":96},"[",[75,862,863],{"class":85},"i",[75,865,866],{"class":96}," +",[75,868,816],{"class":181},[75,870,871],{"class":96},"].",[75,873,874],{"class":150},"map",[75,876,153],{"class":96},[75,878,879],{"class":85},"String",[75,881,168],{"class":96},[75,883,884,887,889,892,894,897,899,902,904,907,909,912,915,918,920,923,925,927,929,932,935,937,940,942,945,948,950,952,955,957,959,961,963],{"class":77,"line":171},[75,885,886],{"class":150},"    out",[75,888,61],{"class":96},[75,890,891],{"class":150},"push",[75,893,153],{"class":96},[75,895,896],{"class":150},"Object",[75,898,61],{"class":96},[75,900,901],{"class":150},"fromEntries",[75,903,153],{"class":96},[75,905,906],{"class":150},"f",[75,908,61],{"class":96},[75,910,911],{"class":150},"flatMap",[75,913,914],{"class":96},"((",[75,916,917],{"class":85},"x",[75,919,111],{"class":96},[75,921,922],{"class":85}," j",[75,924,220],{"class":96},[75,926,223],{"class":81},[75,928,214],{"class":96},[75,930,931],{"class":85},"j",[75,933,934],{"class":96}," %",[75,936,841],{"class":181},[75,938,939],{"class":96}," ?",[75,941,327],{"class":96},[75,943,944],{"class":96}," :",[75,946,947],{"class":96}," [[",[75,949,917],{"class":85},[75,951,111],{"class":96},[75,953,954],{"class":85}," f",[75,956,860],{"class":96},[75,958,931],{"class":85},[75,960,866],{"class":96},[75,962,816],{"class":181},[75,964,965],{"class":96},"]]]))));\n",[75,967,968],{"class":77,"line":192},[75,969,970],{"class":96},"  }\n",[75,972,973,976,979],{"class":77,"line":197},[75,974,975],{"class":81},"  return",[75,977,978],{"class":85}," out",[75,980,97],{"class":96},[75,982,983],{"class":77,"line":203},[75,984,746],{"class":96},[75,986,987],{"class":77,"line":635},[75,988,131],{"emptyLinePlaceholder":130},[75,990,991,993,995,998,1000,1002,1004,1007,1009,1012,1014,1017,1019],{"class":77,"line":661},[75,992,638],{"class":81},[75,994,641],{"class":81},[75,996,997],{"class":150}," recall",[75,999,153],{"class":96},[75,1001,463],{"class":85},[75,1003,111],{"class":96},[75,1005,1006],{"class":85}," query",[75,1008,111],{"class":96},[75,1010,1011],{"class":85}," k",[75,1013,211],{"class":96},[75,1015,1016],{"class":181}," 3",[75,1018,220],{"class":96},[75,1020,658],{"class":96},[75,1022,1023,1025,1028,1030,1032,1034,1036,1039],{"class":77,"line":703},[75,1024,790],{"class":81},[75,1026,1027],{"class":140}," reply ",[75,1029,144],{"class":96},[75,1031,427],{"class":81},[75,1033,285],{"class":150},[75,1035,61],{"class":96},[75,1037,1038],{"class":150},"callBuffer",[75,1040,490],{"class":96},[75,1042,1043,1046,1048,1050],{"class":77,"line":743},[75,1044,1045],{"class":92},"    \"FT.SEARCH\"",[75,1047,111],{"class":96},[75,1049,500],{"class":92},[75,1051,357],{"class":96},[75,1053,1055,1058,1060,1062,1064,1067,1069,1072,1074,1077],{"class":77,"line":1054},13,[75,1056,1057],{"class":92},"    `@user:{",[75,1059,682],{"class":96},[75,1061,463],{"class":85},[75,1063,691],{"class":96},[75,1065,1066],{"class":92},"}=>[KNN ",[75,1068,682],{"class":96},[75,1070,1071],{"class":85},"k",[75,1073,691],{"class":96},[75,1075,1076],{"class":92}," @embedding $vec AS dist]`",[75,1078,357],{"class":96},[75,1080,1082,1085,1087,1090,1092,1095,1097,1099,1101,1103,1105,1107,1110],{"class":77,"line":1081},14,[75,1083,1084],{"class":92},"    \"PARAMS\"",[75,1086,111],{"class":96},[75,1088,1089],{"class":92}," \"2\"",[75,1091,111],{"class":96},[75,1093,1094],{"class":92}," \"vec\"",[75,1096,111],{"class":96},[75,1098,208],{"class":150},[75,1100,153],{"class":96},[75,1102,282],{"class":81},[75,1104,731],{"class":150},[75,1106,153],{"class":96},[75,1108,1109],{"class":85},"query",[75,1111,1112],{"class":96},")),\n",[75,1114,1116,1119,1121,1124,1126,1129,1131,1133,1135,1138,1140,1143],{"class":77,"line":1115},15,[75,1117,1118],{"class":92},"    \"RETURN\"",[75,1120,111],{"class":96},[75,1122,1123],{"class":92}," \"4\"",[75,1125,111],{"class":96},[75,1127,1128],{"class":92}," \"$.text\"",[75,1130,111],{"class":96},[75,1132,544],{"class":92},[75,1134,111],{"class":96},[75,1136,1137],{"class":92}," \"text\"",[75,1139,111],{"class":96},[75,1141,1142],{"class":92}," \"dist\"",[75,1144,357],{"class":96},[75,1146,1148,1151,1153,1155],{"class":77,"line":1147},16,[75,1149,1150],{"class":92},"    \"DIALECT\"",[75,1152,111],{"class":96},[75,1154,1089],{"class":92},[75,1156,357],{"class":96},[75,1158,1160],{"class":77,"line":1159},17,[75,1161,1162],{"class":96},"  );\n",[75,1164,1166,1168,1170,1172,1174,1176,1179,1181,1183,1185,1188,1190,1192,1195,1197,1200,1203,1205,1207,1209],{"class":77,"line":1165},18,[75,1167,975],{"class":81},[75,1169,773],{"class":150},[75,1171,153],{"class":96},[75,1173,778],{"class":85},[75,1175,246],{"class":96},[75,1177,1178],{"class":150},"sort",[75,1180,914],{"class":96},[75,1182,49],{"class":85},[75,1184,111],{"class":96},[75,1186,1187],{"class":85}," b",[75,1189,220],{"class":96},[75,1191,223],{"class":81},[75,1193,1194],{"class":140}," a",[75,1196,61],{"class":96},[75,1198,1199],{"class":85},"dist",[75,1201,1202],{"class":96}," -",[75,1204,1187],{"class":140},[75,1206,61],{"class":96},[75,1208,1199],{"class":85},[75,1210,168],{"class":96},[75,1212,1214],{"class":77,"line":1213},19,[75,1215,746],{"class":96},[12,1217,1218,1219,1222,1223,1226],{},"Where the two engines differ: Redis does not return KNN hits in distance order by default, and Valkey rejects ",[19,1220,1221],{},"SORTBY dist"," on a JSON index. Sorting in code works on both. ",[19,1224,1225],{},"COSINE"," distance is 0 for identical vectors and grows as they diverge.",[37,1228,1230],{"id":1229},"semantic-cache","Semantic cache",[12,1232,1233],{},"Embed the prompt and look up the nearest cached one. Under your threshold, return its answer; otherwise call the LLM and store the result with a TTL. Start near 0.1 and tune it on your own prompts: too high returns wrong answers, too low never hits.",[66,1235,1237],{"className":68,"code":1236,"language":70,"meta":71,"style":71},"await client.call(\n  \"FT.CREATE\", \"idx:cache\", \"ON\", \"JSON\", \"PREFIX\", \"1\", \"cache:\",\n  \"SCHEMA\", \"$.embedding\", \"AS\", \"embedding\", \"VECTOR\", \"HNSW\", \"6\",\n  \"TYPE\", \"FLOAT32\", \"DIM\", String(DIM), \"DISTANCE_METRIC\", \"COSINE\",\n);\n\nasync function cachedAnswer(prompt, callLlm, threshold = 0.1) {\n  const vec = await embed(prompt);\n  const reply = await client.callBuffer(\n    \"FT.SEARCH\", \"idx:cache\", \"*=>[KNN 1 @embedding $vec AS dist]\",\n    \"PARAMS\", \"2\", \"vec\", blob(vec),\n    \"RETURN\", \"4\", \"$.response\", \"AS\", \"response\", \"dist\",\n    \"DIALECT\", \"2\",\n  );\n  const [best] = rows(reply);\n  if (best && Number(best.dist) \u003C threshold) return best.response;\n\n  const response = await callLlm(prompt);\n  const key = `cache:${randomUUID()}`;\n  await client.call(\"JSON.SET\", key, \"$\", JSON.stringify({ prompt, response, embedding: vec }));\n  await client.expire(key, 3600);\n  return response;\n}\n",[19,1238,1239,1251,1283,1314,1344,1348,1352,1385,1404,1422,1437,1461,1489,1499,1503,1526,1570,1574,1593,1617,1670,1693,1702],{"__ignoreMap":71},[75,1240,1241,1243,1245,1247,1249],{"class":77,"line":78},[75,1242,282],{"class":81},[75,1244,285],{"class":150},[75,1246,61],{"class":96},[75,1248,290],{"class":150},[75,1250,490],{"class":96},[75,1252,1253,1255,1257,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1281],{"class":77,"line":100},[75,1254,495],{"class":92},[75,1256,111],{"class":96},[75,1258,1259],{"class":92}," \"idx:cache\"",[75,1261,111],{"class":96},[75,1263,505],{"class":92},[75,1265,111],{"class":96},[75,1267,510],{"class":92},[75,1269,111],{"class":96},[75,1271,515],{"class":92},[75,1273,111],{"class":96},[75,1275,520],{"class":92},[75,1277,111],{"class":96},[75,1279,1280],{"class":92}," \"cache:\"",[75,1282,357],{"class":96},[75,1284,1285,1287,1289,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312],{"class":77,"line":127},[75,1286,532],{"class":92},[75,1288,111],{"class":96},[75,1290,1291],{"class":92}," \"$.embedding\"",[75,1293,111],{"class":96},[75,1295,544],{"class":92},[75,1297,111],{"class":96},[75,1299,569],{"class":92},[75,1301,111],{"class":96},[75,1303,574],{"class":92},[75,1305,111],{"class":96},[75,1307,579],{"class":92},[75,1309,111],{"class":96},[75,1311,584],{"class":92},[75,1313,357],{"class":96},[75,1315,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342],{"class":77,"line":134},[75,1317,591],{"class":92},[75,1319,111],{"class":96},[75,1321,596],{"class":92},[75,1323,111],{"class":96},[75,1325,601],{"class":92},[75,1327,111],{"class":96},[75,1329,606],{"class":150},[75,1331,153],{"class":96},[75,1333,611],{"class":140},[75,1335,614],{"class":96},[75,1337,617],{"class":92},[75,1339,111],{"class":96},[75,1341,622],{"class":92},[75,1343,357],{"class":96},[75,1345,1346],{"class":77,"line":171},[75,1347,168],{"class":96},[75,1349,1350],{"class":77,"line":192},[75,1351,131],{"emptyLinePlaceholder":130},[75,1353,1354,1356,1358,1361,1363,1366,1368,1371,1373,1376,1378,1381,1383],{"class":77,"line":197},[75,1355,638],{"class":81},[75,1357,641],{"class":81},[75,1359,1360],{"class":150}," cachedAnswer",[75,1362,153],{"class":96},[75,1364,1365],{"class":85},"prompt",[75,1367,111],{"class":96},[75,1369,1370],{"class":85}," callLlm",[75,1372,111],{"class":96},[75,1374,1375],{"class":85}," threshold",[75,1377,211],{"class":96},[75,1379,1380],{"class":181}," 0.1",[75,1382,220],{"class":96},[75,1384,658],{"class":96},[75,1386,1387,1389,1392,1394,1396,1398,1400,1402],{"class":77,"line":203},[75,1388,790],{"class":81},[75,1390,1391],{"class":140}," vec ",[75,1393,144],{"class":96},[75,1395,427],{"class":81},[75,1397,731],{"class":150},[75,1399,153],{"class":96},[75,1401,1365],{"class":85},[75,1403,168],{"class":96},[75,1405,1406,1408,1410,1412,1414,1416,1418,1420],{"class":77,"line":635},[75,1407,790],{"class":81},[75,1409,1027],{"class":140},[75,1411,144],{"class":96},[75,1413,427],{"class":81},[75,1415,285],{"class":150},[75,1417,61],{"class":96},[75,1419,1038],{"class":150},[75,1421,490],{"class":96},[75,1423,1424,1426,1428,1430,1432,1435],{"class":77,"line":661},[75,1425,1045],{"class":92},[75,1427,111],{"class":96},[75,1429,1259],{"class":92},[75,1431,111],{"class":96},[75,1433,1434],{"class":92}," \"*=>[KNN 1 @embedding $vec AS dist]\"",[75,1436,357],{"class":96},[75,1438,1439,1441,1443,1445,1447,1449,1451,1453,1455,1458],{"class":77,"line":703},[75,1440,1084],{"class":92},[75,1442,111],{"class":96},[75,1444,1089],{"class":92},[75,1446,111],{"class":96},[75,1448,1094],{"class":92},[75,1450,111],{"class":96},[75,1452,208],{"class":150},[75,1454,153],{"class":96},[75,1456,1457],{"class":85},"vec",[75,1459,1460],{"class":96},"),\n",[75,1462,1463,1465,1467,1469,1471,1474,1476,1478,1480,1483,1485,1487],{"class":77,"line":743},[75,1464,1118],{"class":92},[75,1466,111],{"class":96},[75,1468,1123],{"class":92},[75,1470,111],{"class":96},[75,1472,1473],{"class":92}," \"$.response\"",[75,1475,111],{"class":96},[75,1477,544],{"class":92},[75,1479,111],{"class":96},[75,1481,1482],{"class":92}," \"response\"",[75,1484,111],{"class":96},[75,1486,1142],{"class":92},[75,1488,357],{"class":96},[75,1490,1491,1493,1495,1497],{"class":77,"line":1054},[75,1492,1150],{"class":92},[75,1494,111],{"class":96},[75,1496,1089],{"class":92},[75,1498,357],{"class":96},[75,1500,1501],{"class":77,"line":1081},[75,1502,1162],{"class":96},[75,1504,1505,1507,1510,1513,1516,1518,1520,1522,1524],{"class":77,"line":1115},[75,1506,790],{"class":81},[75,1508,1509],{"class":96}," [",[75,1511,1512],{"class":140},"best",[75,1514,1515],{"class":96},"]",[75,1517,211],{"class":96},[75,1519,773],{"class":150},[75,1521,153],{"class":96},[75,1523,778],{"class":85},[75,1525,168],{"class":96},[75,1527,1528,1531,1533,1535,1538,1541,1543,1545,1547,1549,1551,1553,1555,1557,1560,1563,1565,1568],{"class":77,"line":1147},[75,1529,1530],{"class":81},"  if",[75,1532,214],{"class":96},[75,1534,1512],{"class":85},[75,1536,1537],{"class":96}," &&",[75,1539,1540],{"class":150}," Number",[75,1542,153],{"class":96},[75,1544,1512],{"class":140},[75,1546,61],{"class":96},[75,1548,1199],{"class":85},[75,1550,220],{"class":96},[75,1552,823],{"class":96},[75,1554,1375],{"class":85},[75,1556,220],{"class":96},[75,1558,1559],{"class":81}," return",[75,1561,1562],{"class":140}," best",[75,1564,61],{"class":96},[75,1566,1567],{"class":85},"response",[75,1569,97],{"class":96},[75,1571,1572],{"class":77,"line":1159},[75,1573,131],{"emptyLinePlaceholder":130},[75,1575,1576,1578,1581,1583,1585,1587,1589,1591],{"class":77,"line":1165},[75,1577,790],{"class":81},[75,1579,1580],{"class":140}," response ",[75,1582,144],{"class":96},[75,1584,427],{"class":81},[75,1586,1370],{"class":150},[75,1588,153],{"class":96},[75,1590,1365],{"class":85},[75,1592,168],{"class":96},[75,1594,1595,1597,1600,1602,1605,1607,1609,1611,1613,1615],{"class":77,"line":1213},[75,1596,790],{"class":81},[75,1598,1599],{"class":140}," key ",[75,1601,144],{"class":96},[75,1603,1604],{"class":92}," `cache:",[75,1606,682],{"class":96},[75,1608,685],{"class":150},[75,1610,688],{"class":96},[75,1612,691],{"class":96},[75,1614,694],{"class":92},[75,1616,97],{"class":96},[75,1618,1620,1622,1624,1626,1628,1630,1632,1634,1637,1639,1641,1643,1645,1647,1649,1651,1654,1656,1659,1661,1663,1665,1668],{"class":77,"line":1619},20,[75,1621,664],{"class":81},[75,1623,285],{"class":150},[75,1625,61],{"class":96},[75,1627,290],{"class":150},[75,1629,153],{"class":96},[75,1631,295],{"class":92},[75,1633,111],{"class":96},[75,1635,1636],{"class":85}," key",[75,1638,111],{"class":96},[75,1640,305],{"class":92},[75,1642,111],{"class":96},[75,1644,310],{"class":150},[75,1646,61],{"class":96},[75,1648,315],{"class":150},[75,1650,318],{"class":96},[75,1652,1653],{"class":85}," prompt",[75,1655,111],{"class":96},[75,1657,1658],{"class":85}," response",[75,1660,111],{"class":96},[75,1662,724],{"class":85},[75,1664,324],{"class":96},[75,1666,1667],{"class":85}," vec",[75,1669,330],{"class":96},[75,1671,1673,1675,1677,1679,1681,1683,1686,1688,1691],{"class":77,"line":1672},21,[75,1674,664],{"class":81},[75,1676,285],{"class":150},[75,1678,61],{"class":96},[75,1680,399],{"class":150},[75,1682,153],{"class":96},[75,1684,1685],{"class":85},"key",[75,1687,111],{"class":96},[75,1689,1690],{"class":181}," 3600",[75,1692,168],{"class":96},[75,1694,1696,1698,1700],{"class":77,"line":1695},22,[75,1697,975],{"class":81},[75,1699,1658],{"class":85},[75,1701,97],{"class":96},[75,1703,1705],{"class":77,"line":1704},23,[75,1706,746],{"class":96},[37,1708,1710],{"id":1709},"skip-duplicates-with-a-bloom-filter","Skip duplicates with a Bloom filter",[12,1712,1713,1716],{},[19,1714,1715],{},"BF.ADD"," returns 1 for a new item and 0 if it was probably seen. A false positive skips a new document, at the rate you reserve.",[66,1718,1720],{"className":68,"code":1719,"language":70,"meta":71,"style":71},"await client.call(\"BF.RESERVE\", \"seen:docs\", \"0.01\", \"100000\");\nconst digest = (s) => createHash(\"sha256\").update(s).digest(\"hex\");\nconst isNew = (await client.call(\"BF.ADD\", \"seen:docs\", digest(text))) === 1;\n",[19,1721,1722,1754,1800],{"__ignoreMap":71},[75,1723,1724,1726,1728,1730,1732,1734,1737,1739,1742,1744,1747,1749,1752],{"class":77,"line":78},[75,1725,282],{"class":81},[75,1727,285],{"class":150},[75,1729,61],{"class":96},[75,1731,290],{"class":150},[75,1733,153],{"class":96},[75,1735,1736],{"class":92},"\"BF.RESERVE\"",[75,1738,111],{"class":96},[75,1740,1741],{"class":92}," \"seen:docs\"",[75,1743,111],{"class":96},[75,1745,1746],{"class":92}," \"0.01\"",[75,1748,111],{"class":96},[75,1750,1751],{"class":92}," \"100000\"",[75,1753,168],{"class":96},[75,1755,1756,1758,1761,1763,1765,1768,1770,1772,1774,1776,1779,1781,1784,1786,1788,1790,1793,1795,1798],{"class":77,"line":100},[75,1757,137],{"class":81},[75,1759,1760],{"class":150}," digest",[75,1762,211],{"class":96},[75,1764,214],{"class":96},[75,1766,1767],{"class":85},"s",[75,1769,220],{"class":96},[75,1771,223],{"class":81},[75,1773,114],{"class":150},[75,1775,153],{"class":96},[75,1777,1778],{"class":92},"\"sha256\"",[75,1780,246],{"class":96},[75,1782,1783],{"class":150},"update",[75,1785,153],{"class":96},[75,1787,1767],{"class":85},[75,1789,246],{"class":96},[75,1791,1792],{"class":150},"digest",[75,1794,153],{"class":96},[75,1796,1797],{"class":92},"\"hex\"",[75,1799,168],{"class":96},[75,1801,1802,1804,1807,1809,1811,1813,1815,1817,1819,1821,1824,1826,1828,1830,1832,1834,1836,1839,1842,1844],{"class":77,"line":127},[75,1803,137],{"class":81},[75,1805,1806],{"class":140}," isNew ",[75,1808,144],{"class":96},[75,1810,214],{"class":96},[75,1812,282],{"class":81},[75,1814,285],{"class":150},[75,1816,61],{"class":96},[75,1818,290],{"class":150},[75,1820,153],{"class":96},[75,1822,1823],{"class":92},"\"BF.ADD\"",[75,1825,111],{"class":96},[75,1827,1741],{"class":92},[75,1829,111],{"class":96},[75,1831,1760],{"class":150},[75,1833,153],{"class":96},[75,1835,736],{"class":85},[75,1837,1838],{"class":96},")))",[75,1840,1841],{"class":96}," ===",[75,1843,816],{"class":181},[75,1845,97],{"class":96},[37,1847,1849],{"id":1848},"forgetting-a-user","Forgetting a user",[12,1851,1852],{},"Delete a user's memories through the index, then their chats by key pattern. A Bloom filter cannot remove one item, so store only hashes in it.",[66,1854,1856],{"className":68,"code":1855,"language":70,"meta":71,"style":71},"async function forget(user) {\n  for (;;) {\n    const [, ...keys] = await client.call(\n      \"FT.SEARCH\", \"idx:memory\", `@user:{${user}}`,\n      \"NOCONTENT\", \"LIMIT\", \"0\", \"500\", \"DIALECT\", \"2\");\n    if (!keys.length) break;\n    await client.del(...keys);\n  }\n  for await (const keys of client.scanStream({ match: `chat:${user}:*` })) {\n    if (keys.length) await client.del(...keys);\n  }\n}\n",[19,1857,1858,1875,1884,1911,1936,1967,1988,2007,2011,2058,2086,2090],{"__ignoreMap":71},[75,1859,1860,1862,1864,1867,1869,1871,1873],{"class":77,"line":78},[75,1861,638],{"class":81},[75,1863,641],{"class":81},[75,1865,1866],{"class":150}," forget",[75,1868,153],{"class":96},[75,1870,463],{"class":85},[75,1872,220],{"class":96},[75,1874,658],{"class":96},[75,1876,1877,1879,1882],{"class":77,"line":100},[75,1878,803],{"class":81},[75,1880,1881],{"class":96}," (;;)",[75,1883,658],{"class":96},[75,1885,1886,1888,1891,1894,1897,1899,1901,1903,1905,1907,1909],{"class":77,"line":127},[75,1887,850],{"class":81},[75,1889,1890],{"class":96}," [,",[75,1892,1893],{"class":96}," ...",[75,1895,1896],{"class":140},"keys",[75,1898,1515],{"class":96},[75,1900,211],{"class":96},[75,1902,427],{"class":81},[75,1904,285],{"class":150},[75,1906,61],{"class":96},[75,1908,290],{"class":150},[75,1910,490],{"class":96},[75,1912,1913,1916,1918,1920,1922,1925,1927,1929,1931,1934],{"class":77,"line":134},[75,1914,1915],{"class":92},"      \"FT.SEARCH\"",[75,1917,111],{"class":96},[75,1919,500],{"class":92},[75,1921,111],{"class":96},[75,1923,1924],{"class":92}," `@user:{",[75,1926,682],{"class":96},[75,1928,463],{"class":85},[75,1930,691],{"class":96},[75,1932,1933],{"class":92},"}`",[75,1935,357],{"class":96},[75,1937,1938,1941,1943,1946,1948,1951,1953,1956,1958,1961,1963,1965],{"class":77,"line":171},[75,1939,1940],{"class":92},"      \"NOCONTENT\"",[75,1942,111],{"class":96},[75,1944,1945],{"class":92}," \"LIMIT\"",[75,1947,111],{"class":96},[75,1949,1950],{"class":92}," \"0\"",[75,1952,111],{"class":96},[75,1954,1955],{"class":92}," \"500\"",[75,1957,111],{"class":96},[75,1959,1960],{"class":92}," \"DIALECT\"",[75,1962,111],{"class":96},[75,1964,1089],{"class":92},[75,1966,168],{"class":96},[75,1968,1969,1972,1975,1977,1979,1981,1983,1986],{"class":77,"line":192},[75,1970,1971],{"class":81},"    if",[75,1973,1974],{"class":96}," (!",[75,1976,1896],{"class":140},[75,1978,61],{"class":96},[75,1980,831],{"class":85},[75,1982,220],{"class":96},[75,1984,1985],{"class":81}," break",[75,1987,97],{"class":96},[75,1989,1990,1993,1995,1997,2000,2003,2005],{"class":77,"line":197},[75,1991,1992],{"class":81},"    await",[75,1994,285],{"class":150},[75,1996,61],{"class":96},[75,1998,1999],{"class":150},"del",[75,2001,2002],{"class":96},"(...",[75,2004,1896],{"class":85},[75,2006,168],{"class":96},[75,2008,2009],{"class":77,"line":203},[75,2010,970],{"class":96},[75,2012,2013,2015,2017,2019,2021,2024,2027,2029,2031,2034,2036,2039,2041,2044,2046,2048,2050,2053,2056],{"class":77,"line":635},[75,2014,803],{"class":81},[75,2016,427],{"class":81},[75,2018,214],{"class":96},[75,2020,137],{"class":81},[75,2022,2023],{"class":140}," keys ",[75,2025,2026],{"class":81},"of",[75,2028,285],{"class":150},[75,2030,61],{"class":96},[75,2032,2033],{"class":150},"scanStream",[75,2035,318],{"class":96},[75,2037,2038],{"class":85}," match",[75,2040,324],{"class":96},[75,2042,2043],{"class":92}," `chat:",[75,2045,682],{"class":96},[75,2047,463],{"class":85},[75,2049,691],{"class":96},[75,2051,2052],{"class":92},":*`",[75,2054,2055],{"class":96}," }))",[75,2057,658],{"class":96},[75,2059,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2080,2082,2084],{"class":77,"line":661},[75,2061,1971],{"class":81},[75,2063,214],{"class":96},[75,2065,1896],{"class":140},[75,2067,61],{"class":96},[75,2069,831],{"class":85},[75,2071,220],{"class":96},[75,2073,427],{"class":81},[75,2075,285],{"class":150},[75,2077,61],{"class":96},[75,2079,1999],{"class":150},[75,2081,2002],{"class":96},[75,2083,1896],{"class":85},[75,2085,168],{"class":96},[75,2087,2088],{"class":77,"line":703},[75,2089,970],{"class":96},[75,2091,2092],{"class":77,"line":743},[75,2093,746],{"class":96},[37,2095,2097],{"id":2096},"sizing","Sizing",[12,2099,2100,2101,2104,2105,2108,2109,2112],{},"A 384-dimension ",[19,2102,2103],{},"FLOAT32"," vector is 1,536 bytes raw. Indexed JSON costs more, because the array is also stored as text and HNSW adds graph links, so load a sample and read ",[19,2106,2107],{},"INFO memory",". Indexes count against ",[19,2110,2111],{},"maxmemory",": Starter has 192 MB of data and 0.1 vCPU, so large index builds are best effort.",[37,2114,2116],{"id":2115},"where-kvnest-falls-short","Where KVNest falls short",[2118,2119,2121,2151],"limits-box",{"title":2120},"Limits",[2122,2123,2124,2131,2145],"ul",{},[2125,2126,2127,2130],"li",{},[15,2128,2129],{},"No persistence on Starter."," It is a cache with no backups, so long-term memory is lost on restart. That is fine for a cache or for memory you can rebuild.",[2125,2132,2133,2136,2137,2140,2141,2144],{},[15,2134,2135],{},"Eviction."," Starter defaults to ",[19,2138,2139],{},"allkeys-lru"," and can evict memories when full. Set ",[19,2142,2143],{},"noeviction"," in Settings if you would rather see write errors.",[2125,2146,2147,2150],{},[15,2148,2149],{},"Single node, preview."," No failover, and Search on 0.1 vCPU is best effort for large indexes.",[12,2152,2153],{},"KVNest Starter is free during the preview, with 256 MiB of memory, hosted at Hetzner in Germany.",[12,2155,2156,2157,57,2161,2165,2166,2170,2171,61],{},"See the ",[49,2158,2160],{"href":2159},"\u002Fdocs\u002Fnodejs","Node.js",[49,2162,2164],{"href":2163},"\u002Fdocs\u002Fpython","Python"," guides, ",[49,2167,2169],{"href":2168},"\u002Fpricing","pricing",", and the ",[49,2172,2174],{"href":2173},"\u002Fcompare\u002Feu-managed-redis","EU managed Redis and Valkey comparison",[2176,2177,2178],"style",{},"html pre.shiki code .sQ0Rz, html code.shiki .sQ0Rz{--shiki-light:#1A32EC;--shiki-default:#1A32EC;--shiki-dark:#83A2FF}html pre.shiki code .sxIm-, html code.shiki .sxIm-{--shiki-light:#1D293C;--shiki-default:#1D293C;--shiki-dark:#DBE9FF}html pre.shiki code .sxAeW, html code.shiki .sxAeW{--shiki-light:#973C00;--shiki-default:#973C00;--shiki-dark:#FFB900}html pre.shiki code .s6SZf, html code.shiki .s6SZf{--shiki-light:#425674;--shiki-default:#425674;--shiki-dark:#9CAFCE}html pre.shiki code .sq_S-, html code.shiki .sq_S-{--shiki-light:#0E1625;--shiki-default:#0E1625;--shiki-dark:#CCE0FF}html pre.shiki code .sdT5u, html code.shiki .sdT5u{--shiki-light:#005F78;--shiki-default:#005F78;--shiki-dark:#63D6F1}html pre.shiki code .sLNte, html code.shiki .sLNte{--shiki-light:#132BCE;--shiki-default:#132BCE;--shiki-dark:#A1BAFF}html pre.shiki code .sgbRl, html code.shiki .sgbRl{--shiki-light:#425674;--shiki-default:#425674;--shiki-dark:#819DCD}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":71,"searchDepth":100,"depth":127,"links":2180},[2181,2182,2183,2187,2188,2189,2190,2191],{"id":39,"depth":100,"text":40},{"id":254,"depth":100,"text":255},{"id":451,"depth":100,"text":452,"children":2184},[2185,2186],{"id":456,"depth":127,"text":457},{"id":749,"depth":127,"text":750},{"id":1229,"depth":100,"text":1230},{"id":1709,"depth":100,"text":1710},{"id":1848,"depth":100,"text":1849},{"id":2096,"depth":100,"text":2097},{"id":2115,"depth":100,"text":2116},"2026-11-05","Chat history in JSON, long-term memory with HNSW vector search, a semantic cache and Bloom dedupe on one instance. Node.js code for KVNest, hosted in Germany.","md",[2196,2199,2202,2205,2208],{"question":2197,"answer":2198},"Can Valkey or Redis replace a vector database?","For a small corpus, yes: the instance stores vectors, indexes them with HNSW or FLAT, and filters by tags and numbers. It is held in memory, so it suits thousands to low millions of vectors, not billions.",{"question":2200,"answer":2201},"Does Valkey support vector search?","Yes, through the Valkey Search module (FT.CREATE and FT.SEARCH, HNSW and FLAT, hash and JSON data). KVNest loads it from the valkey-bundle image when modules are on.",{"question":2203,"answer":2204},"What is a semantic cache?","A cache keyed by meaning instead of exact text. You embed each prompt, look up the nearest earlier prompt, and return its stored answer if the distance is under a threshold.",{"question":2206,"answer":2207},"How many embeddings fit in 256 MiB?","Starter allows 192 MB of data. A 384-dimension FLOAT32 vector is 1,536 bytes, but a JSON document also stores the numbers as text and the index adds its own copy and graph, so measure with INFO memory on your own data.",{"question":2209,"answer":2210},"Is it GDPR-friendly to keep chat history in a cache?","The data stays in Germany, you can expire it with a TTL, and you can delete one user's keys on request. Whether that meets your obligations is a legal question; this is not legal advice.",null,{},"\u002Fuse-cases\u002Fai-agent-memory","agents",[],[2217,2173],"\u002Fuse-cases\u002Fsessions-and-rate-limiting",{"title":5,"description":2193},[2220,2223,2226,2229,2232,2235],{"label":2221,"url":2222,"checked":2192},"Valkey Search overview","https:\u002F\u002Fvalkey.io\u002Ftopics\u002Fsearch\u002F",{"label":2224,"url":2225,"checked":2192},"FT.CREATE, Valkey documentation","https:\u002F\u002Fvalkey.io\u002Fcommands\u002Fft.create\u002F",{"label":2227,"url":2228,"checked":2192},"FT.SEARCH, Valkey documentation","https:\u002F\u002Fvalkey.io\u002Fcommands\u002Fft.search\u002F",{"label":2230,"url":2231,"checked":2192},"Vector search concepts, Redis Query Engine","https:\u002F\u002Fredis.io\u002Fdocs\u002Flatest\u002Fdevelop\u002Fai\u002Fsearch-and-query\u002Fvectors\u002F",{"label":2233,"url":2234,"checked":2192},"Valkey JSON","https:\u002F\u002Fvalkey.io\u002Ftopics\u002Fvalkey-json\u002F",{"label":2236,"url":2237,"checked":2192},"Valkey Bloom filters","https:\u002F\u002Fvalkey.io\u002Ftopics\u002Fbloomfilters\u002F","use-cases\u002Fai-agent-memory","iLEgIQKMyTLPYX4NL9mBbkxOlYKkngDFXHTicKIObw4",[2241,2245],{"path":2217,"title":2242,"answer":2243,"description":2244,"published":130},"Sessions, rate limiting and stampede-safe caching on Valkey or Redis","Store each session under its own key with a TTL, rate-limit with INCR plus EXPIRE ... NX per client and window, and fill caches under a SET ... NX PX lock. On KVNest's free Starter plan, set the eviction policy to noeviction first, or the cache policy can silently drop sessions.","Keep sessions with a TTL, limit requests with INCR and EXPIRE, and cache without stampedes. Runnable Node.js code for Nuxt and Express on KVNest.",{"path":2173,"title":2246,"answer":2247,"description":2248,"published":130},"EU managed Redis & Valkey: 11 providers compared (Nov 2026)","Seven of the 11 providers keep both the company and the data in the EU: Aiven, OVHcloud, Scaleway, Clever Cloud, UpCloud, Stackhero and KVNest. UpCloud is the cheapest with data on disk, at €8 a month for 1 GB. As of 4 November 2026, KVNest is the only one of the 11 that combines a free tier, a hosted MCP server and a choice of Redis or Valkey.","Who owns them, where your data lives, what the free tier and smallest plan cost, and who runs a hosted MCP server. Checked against each provider's own pages.",1793839178679]