{"id":12685,"date":"2026-07-08T07:56:27","date_gmt":"2026-07-08T14:56:27","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cosmosdb\/?p=12685"},"modified":"2026-07-08T08:08:30","modified_gmt":"2026-07-08T15:08:30","slug":"cosmos-db-agent-memory","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cosmosdb\/cosmos-db-agent-memory\/","title":{"rendered":"Building on Vercel&#8217;s eve + Azure Cosmos DB: An Agent That Remembers"},"content":{"rendered":"<p>Most &#8220;AI agent&#8221; demos forget everything the moment the process exits. That&#8217;s fine for a toy project, but useless for anything real. An agent that helps you write, triage, or support needs two things a language model alone can&#8217;t give it: durable state and the ability to recall the right context by meaning. This post shows how to build exactly that by integrating two pieces that fit together surprisingly well:<\/p>\n<p><a href=\"https:\/\/www.npmjs.com\/package\/eve\"><strong>Eve<\/strong><\/a> \u2014 Vercel&#8217;s filesystem-first agent platform. Drop a file in agent\/tools\/, and it becomes a tool the model can call.<\/p>\n<p><a href=\"https:\/\/www.npmjs.com\/package\/@azure\/cosmos\"><strong>Azure Cosmos DB JavaScript SDK<\/strong><\/a> \u2014 the official, promise-based client for Cosmos DB NoSQL, and the piece that does the heavy lifting here. It&#8217;s a modern, fully typed TypeScript SDK<\/p>\n<p>We&#8217;ll build an agent that authors a Next.js blog <em>and<\/em> keeps a long-term semantic memory in Azure Cosmos DB. Every code sample below is production-shaped, not pseudocode.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/context.png\"><img decoding=\"async\" class=\"alignnone wp-image-12686 size-full\" src=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/context.png\" alt=\"Diagram showing a user request flowing through an AI agent, working memory, search, task agents, and long-term memory storage.\" width=\"1536\" height=\"1024\" srcset=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/context.png 1536w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/context-300x200.png 300w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/context-1024x683.png 1024w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/context-768x512.png 768w\" sizes=\"(max-width: 1536px) 100vw, 1536px\" \/><\/a><\/p>\n<h2>Meet eve: agents as a filesystem<\/h2>\n<p>If you&#8217;ve built with the Vercel AI SDK, eve will feel immediately familiar it\u2019s the same team&#8217;s take on <em>agents<\/em>, and its core idea is refreshingly literal: <strong>your agent is a folder.<\/strong> There&#8217;s no graph DSL to learn and no orchestration boilerplate. You describe the agent with plain files, and eve wires them into a running agent:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/Image.png\"><img decoding=\"async\" class=\"alignnone wp-image-12687 size-full\" src=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/Image.png\" alt=\"Diagram of an agent project folder showing agent.ts for the agent definition, instructions.md for the system prompt, a tools folder with callable TypeScript tools, and a lib folder for shared helper code.\" width=\"1536\" height=\"1024\" srcset=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/Image.png 1536w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/Image-300x200.png 300w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/Image-1024x683.png 1024w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/Image-768x512.png 768w\" sizes=\"(max-width: 1536px) 100vw, 1536px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<ul>\n<li><strong>Tools are just files.<\/strong> Drop a defineTool({&#8230;}) in agent\/tools\/ and it&#8217;s available to the model \u2014 no registration, no wiring. Inputs are described with a Zod schema, and eve generates the JSON schema the model sees.<\/li>\n<li><strong>Instructions are Markdown.<\/strong> md is your system prompt. Editing behavior means editing prose, not code.<\/li>\n<li><strong>Bring your own model.<\/strong> eve runs on the AI SDK&#8217;s provider ecosystem, so you can point it at OpenAI, Anthropic, or as we do here <strong>Azure OpenAI<\/strong><\/li>\n<\/ul>\n<p><strong>agent.ts<\/strong><\/p>\n<pre class=\"prettyprint language-js\"><code class=\"language-js\">import { createAzure } from \"@ai-sdk\/azure\";\r\nimport { defineAgent } from \"eve\";\r\n \r\nconst azure = createAzure({\r\n  baseURL: `${process.env.AZURE_OPENAI_ENDPOINT}\/openai`,\r\n  apiKey: process.env.AZURE_OPENAI_API_KEY,\r\n  apiVersion: \"preview\",\r\n});\r\n \r\nexport default defineAgent({\r\n  model: azure.chat(\"gpt-4.1\"), \/\/ your Azure deployment name\r\n});\r\n<\/code><\/pre>\n<ul>\n<li><strong>Runs locally, ships anywhere.<\/strong> eve dev gives you an interactive chat in the terminal; eve build produces a deployable server. The agent talks to the outsideworld (your blog&#8217;s API, Azure Cosmos DB) through the tools <em>you<\/em><\/li>\n<\/ul>\n<p>What eve deliberately <em>doesn&#8217;t<\/em> give you is persistence. A model call is stateless; when the process restarts, the conversation is gone. That&#8217;s the gap we fill with Azure Cosmos DB \u2014 turning a stateless agent into one that genuinely <strong>remembers<\/strong>.<\/p>\n<h2 style=\"margin-bottom: 10.0pt;\">Why Azure Cosmos DB for agent memory?<\/h2>\n<p>Agent memory has an awkward shape for traditional databases: you write a lot of small items, you read them back by <em>meaning<\/em> (not exact keys), and you want it fast and global. Azure Cosmos DB NoSQL fits this well:<\/p>\n<ul>\n<li><strong>Single-digit-millisecond point reads<\/strong> when you know the id and partition key.<\/li>\n<li><strong>Native vector search<\/strong> \u2014 store embeddings and query with VectorDistance(); no\nseparate vector database to run.<\/li>\n<li><strong>Native TTL<\/strong> \u2014 expire ephemeral memories with zero cleanup jobs.<\/li>\n<li><strong>One SDK, one bill<\/strong> \u2014 the same container holds your app data <em>and<\/em> your vector<\/li>\n<\/ul>\n<h2>Step 1: A singleton client (the #1 SDK rule)<\/h2>\n<p>A CosmosClient owns a connection pool and routing caches. Creating one per request exhausts sockets and adds TLS latency. Create it <strong>once<\/strong> and reuse it:<\/p>\n<pre class=\"prettyprint language-js\"><code class=\"language-js\">\/\/ src\/lib\/cosmos.ts    \r\n    import { CosmosClient } from \"@azure\/cosmos\";    \r\n    import { DefaultAzureCredential } from \"@azure\/identity\";    \r\n         \r\n    type CosmosGlobal = typeof globalThis &amp; { __client?: CosmosClient };    \r\n         \r\n    export function getCosmosClient(): CosmosClient {    \r\n      const g = globalThis as CosmosGlobal;    \r\n      if (!g.__client) {    \r\n        g.__client = new CosmosClient({    \r\n          endpoint: process.env.COSMOS_ENDPOINT!,    \r\n          \/\/ Prefer Entra ID over keys. Falls back to a key only if provided.    \r\n          aadCredentials: new DefaultAzureCredential(),    \r\n          connectionPolicy: {    \r\n            \/\/ Let the SDK absorb transient 429s with backoff.    \r\n            retryOptions: { maxRetryAttemptCount: 9, maxWaitTimeInSeconds: 30 },    \r\n          },    \r\n        });    \r\n      }    \r\n      return g.__client;    \r\n    }    \r\n<\/code><\/pre>\n<p>Caching on\u00a0<code class=\"language-js\">globalThis\u00a0<\/code>matters in dev: frameworks like Next.js reset module state on hot-reload, which would otherwise leak a new client on every save.<\/p>\n<h2>Step 2: Model for cheap reads<\/h2>\n<p>Pick a partition key that is <strong>high-cardinality<\/strong> and <strong>immutable<\/strong>, and make the item id equal to it where you can \u2014 then single-item fetches are <strong>1 RU point reads<\/strong> instead of queries:<\/p>\n<pre class=\"prettyprint language-js\"><code class=\"language-js\">    \/\/ A blog post: id === slug, partition key is \/slug.    \r\n    const { resource: post } = await container    \r\n      .item(slug, slug) \/\/ (id, partitionKey)    \r\n      .read(); \/\/ ~1 RU, single-digit ms    \r\n<\/code><\/pre>\n<p>For lists, use <strong>continuation tokens<\/strong>, never OFFSET\/LIMIT:<\/p>\n<pre class=\"prettyprint language-default\"><code class=\"language-default\">    const iterator = container.items    \r\n      .query({ query: \"SELECT c.slug, c.title FROM c WHERE c.status = 'published'\" })    \r\n      .getAsyncIterator();    \r\n    \/\/ ...or .query(spec).fetchNext() and pass resource.continuationToken to the client.    \r\n<\/code><\/pre>\n<h2>Step 3: Turn the container into a vector memory store<\/h2>\n<p>Here&#8217;s where it gets fun. To store embeddings and search them, define a <strong>vector embedding policy<\/strong> and a <strong>vector index<\/strong> at container-creation time (the policy is immutable afterward):<\/p>\n<pre class=\"prettyprint language-js\"><code class=\"language-js\">         \r\n    import {    \r\n      VectorEmbeddingDataType,    \r\n      VectorEmbeddingDistanceFunction,    \r\n      VectorIndexType,    \r\n    } from \"@azure\/cosmos\";    \r\n         \r\n    await database.containers.createIfNotExists({    \r\n      id: \"memory\",    \r\n      partitionKey: { paths: [\"\/sessionId\"] }, \/\/ one conversation per partition    \r\n      defaultTtl: -1, \/\/ TTL enabled; set per-item `ttl` for ephemeral memories    \r\n      vectorEmbeddingPolicy: {    \r\n        vectorEmbeddings: [    \r\n          {    \r\n            path: \"\/embedding\",    \r\n            dataType: VectorEmbeddingDataType.Float32,    \r\n            dimensions: 1536, \/\/ text-embedding-3-small    \r\n            distanceFunction: VectorEmbeddingDistanceFunction.Cosine,    \r\n          },    \r\n        ],    \r\n      },    \r\n      indexingPolicy: {    \r\n        includedPaths: [{ path: \"\/*\" }],    \r\n        \/\/ CRITICAL: keep the raw vector out of the regular index (saves write RUs).    \r\n        excludedPaths: [{ path: \"\/embedding\/*\" }],    \r\n        vectorIndexes: [{ path: \"\/embedding\", type: VectorIndexType.QuantizedFlat }],    \r\n      },    \r\n<\/code><\/pre>\n<p><strong>Storing a memory<\/strong> \u2014 embed the text, then create the item:<\/p>\n<pre class=\"prettyprint language-js\"><code class=\"language-js\">    import { randomUUID } from \"node:crypto\";             \r\n    async function appendMemory(sessionId: string, content: string) {    \r\n      const embedding = await embedText(content); \/\/ Azure OpenAI embeddings \u2192 number[]    \r\n      await container.items.create({    \r\n        id: randomUUID(),    \r\n        sessionId,    \r\n        type: \"memory\",    \r\n        content,    \r\n        embedding,    \r\n        createdAt: new Date().toISOString(),    \r\n      });    \r\n    }             \r\n<\/code><\/pre>\n<p><strong>Recalling by meaning<\/strong> \u2014 VectorDistance() with ORDER BY, a literal TOP, and a parameterized query vector (so the query plan caches):<\/p>\n<pre class=\"prettyprint language-js\"><code class=\"language-js\">    async function recall(sessionId: string, query: string, k = 5) {    \r\n      const vector = await embedText(query);    \r\n      const { resources } = await container.items    \r\n        .query(    \r\n          {    \r\n            query:    \r\n              `SELECT TOP ${k} c.content, VectorDistance(c.embedding, @vec) AS score ` +    \r\n              `FROM c WHERE c.type = \"memory\" AND c.sessionId = @sessionId ` +    \r\n              `ORDER BY VectorDistance(c.embedding, @vec)`,    \r\n            parameters: [    \r\n              { name: \"@vec\", value: vector },    \r\n              { name: \"@sessionId\", value: sessionId },    \r\n            ],    \r\n          },    \r\n          { partitionKey: sessionId }, \/\/ single-partition \u2192 cheap and fast    \r\n        )    \r\n        .fetchAll();    \r\n      return resources; \/\/ nearest memories, most similar first    \r\n    }    \r\n<\/code><\/pre>\n<p>Because we filter on the partition key, this stays a <strong>single-partition query<\/strong> \u2014 exactly what you want on a hot path.<\/p>\n<h2>Step 4: Expose it to the agent as an eve tool<\/h2>\n<p>eve&#8217;s model is delightfully simple: <strong>every file in `agent\/tools\/` is a tool.<\/strong> You describe its inputs with Zod, and eve hands the model a JSON schema automatically.<\/p>\n<pre class=\"prettyprint language-js\"><code class=\"language-js\">         \r\n    \/\/ agent\/tools\/recall.ts    \r\n    import { defineTool } from \"eve\/tools\";    \r\n    import { z } from \"zod\";    \r\n    import { blogApiUrl, authHeaders } from \"..\/lib\/blog-client.js\";    \r\n         \r\n    export default defineTool({    \r\n      description:    \r\n        \"Recall the most relevant saved memories for a query using vector search.\",    \r\n      inputSchema: z.object({    \r\n        query: z.string().min(1).describe(\"What to search your memory for.\"),    \r\n        sessionId: z.string().optional(),    \r\n        k: z.number().int().min(1).max(20).default(5),    \r\n      }),    \r\n      async execute({ query, sessionId, k }) {    \r\n        const url = new URL(blogApiUrl(\"\/api\/memory\")); \/\/ resolves BLOG_API_URL    \r\n        url.searchParams.set(\"q\", query);    \r\n        url.searchParams.set(\"k\", String(k));    \r\n        if (sessionId) url.searchParams.set(\"sessionId\", sessionId);    \r\n        const res = await fetch(url, { headers: authHeaders() });    \r\n        const { results } = await res.json();    \r\n        return { ok: true, memories: results };    \r\n      },    \r\n<\/code><\/pre>\n<p>Now the agent can decide on its own, to call recall before answering \u2014 and because the memories live in Azure Cosmos DB, they persist across sessions, deploys, and regions.<\/p>\n<h2 style=\"margin-bottom: 10.0pt;\">The full loop<\/h2>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/full-loop.png\"><img decoding=\"async\" class=\"alignnone wp-image-12688 size-full\" src=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/full-loop.png\" alt=\"Diagram showing an agent memory flow where an AI agent sends remember and recall requests through an authenticated API, creates text embeddings with Azure OpenAI, and stores or searches memories in Azure Cosmos DB using vector search.\" width=\"1536\" height=\"1024\" srcset=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/full-loop.png 1536w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/full-loop-300x200.png 300w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/full-loop-1024x683.png 1024w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/07\/full-loop-768x512.png 768w\" sizes=\"(max-width: 1536px) 100vw, 1536px\" \/><\/a><\/p>\n<p>That&#8217;s a complete, durable, semantically searchable memory in well under 200 lines no extra infrastructure, no second database.<\/p>\n<h2>Try it<\/h2>\n<p>The complete, runnable source for everything in this post lives in the <a href=\"https:\/\/github.com\/AzureCosmosDB\/eve-cosmos-memory\"><strong>AzureCosmosDB\/eve-cosmos-memory repository<\/strong><\/a> clone it and follow along:<\/p>\n<pre class=\"prettyprint language-js\"><code class=\"language-js\">    npm install    \r\n    npm run seed     # creates the DB, blog container, and vector memory container    \r\n    npm run dev      # the blog (Next.js on Vercel)    \r\n    npm run agent    # chat with the eve agent \u2014 ask it to remember and recall    \r\n<\/code><\/pre>\n<p>Give your agent a memory. The Azure Cosmos DB JavaScript SDK makes the durable,\nglobal, vector-searchable part almost boring which is exactly what you want from\ninfrastructure.<\/p>\n<p><strong>About Azure Cosmos DB<\/strong><\/p>\n<p>Azure Cosmos DB is a fully managed and serverless NoSQL and vector database for modern app development, including AI applications. With its SLA-backed speed and availability as well as instant dynamic scalability, it is ideal for real-time NoSQL and MongoDB applications that require high performance and distributed computing over massive volumes of NoSQL and vector data.<\/p>\n<p>To stay in the loop on Azure Cosmos DB updates, follow us on\u00a0<a href=\"https:\/\/twitter.com\/AzureCosmosDB\">X<\/a>,\u00a0<a href=\"https:\/\/aka.ms\/AzureCosmosDBYouTube\">YouTube<\/a>, and\u00a0<a href=\"https:\/\/www.linkedin.com\/company\/azure-cosmos-db\/\">LinkedIn<\/a>.\u00a0 Join the discussion with other developers on the\u00a0<a href=\"https:\/\/discord.gg\/pczdC2SU\">#nosql channel on the Microsoft Open Source Discord<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most &#8220;AI agent&#8221; demos forget everything the moment the process exits. That&#8217;s fine for a toy project, but useless for anything real. An agent that helps you write, triage, or support needs two things a language model alone can&#8217;t give it: durable state and the ability to recall the right context by meaning. This post [&hellip;]<\/p>\n","protected":false},"author":80443,"featured_media":12689,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1610,1980],"tags":[2030,1864,2029,1859,1872,2027],"class_list":["post-12685","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-azure-cosmos-db","tag-agentmemory","tag-azurecosmosdb","tag-eve","tag-javascript","tag-nosql","tag-semantic-search"],"acf":[],"blog_post_summary":"<p>Most &#8220;AI agent&#8221; demos forget everything the moment the process exits. That&#8217;s fine for a toy project, but useless for anything real. An agent that helps you write, triage, or support needs two things a language model alone can&#8217;t give it: durable state and the ability to recall the right context by meaning. This post [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts\/12685","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/users\/80443"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/comments?post=12685"}],"version-history":[{"count":2,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts\/12685\/revisions"}],"predecessor-version":[{"id":12693,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/posts\/12685\/revisions\/12693"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/media\/12689"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/media?parent=12685"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/categories?post=12685"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-json\/wp\/v2\/tags?post=12685"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}