A small, production-shaped example that combines three things:
- eve — Vercel's filesystem-first framework for durable AI agents — as the author of blog posts.
- Azure Cosmos DB (via the
@azure/cosmosJavaScript SDK) as the data layer, written to follow the Cosmos DB best practices. - Next.js on Vercel as the web app that serves the blog.
The seeded post itself is an article explaining how these pieces fit together.
┌────────────┐ POST /api/posts ┌──────────────────┐ @azure/cosmos ┌─────────────┐
│ eve agent │ ───────────────────────▶ │ Next.js (Vercel)│ ────────────────▶ │ Cosmos DB │
│ (author) │ Bearer AUTHOR_API_KEY │ API routes + UI │ point reads / │ container │
└────────────┘ └──────────────────┘ paginated query └─────────────┘
The agent and the web app are decoupled: the agent authors posts over HTTP, so the same tools work against localhost or your deployed Vercel URL.
| Path | What it is |
|---|---|
| src/lib/cosmos.ts | Singleton CosmosClient (Entra ID or key auth) |
| src/lib/posts.ts | Repository: point reads, continuation-token pagination, ETag publish |
| src/lib/auth.ts | Constant-time bearer-token check for writes |
| src/app/page.tsx | Home page — paginated list of published posts |
| src/app/posts/[slug]/page.tsx | Single post — 1 RU point read + Markdown render |
| src/app/api/posts/route.ts | GET list · POST create |
| src/app/api/posts/[slug]/route.ts | GET single post |
| src/app/api/posts/[slug]/publish/route.ts | POST publish a draft |
| agent/ | The eve agent: instructions.md, agent.ts, and tools/ |
| content/seed-post.ts | The seeded blog article |
| scripts/seed.mts | Creates the DB/container and upserts the article |
- Singleton client — one
CosmosClientper process, cached onglobalThisin dev. - High-cardinality, immutable partition key —
/slug(unique per post, never changes). - Point reads — single posts read with
container.item(slug, slug).read()(1 RU, no query engine). - Continuation-token pagination — never
OFFSET/LIMIT; empty tokens guarded toundefined. - Projections — the list query selects only card fields, not the full body.
- Atomic duplicate rejection —
items.createreturns 409 on an existing slug. - Optimistic concurrency — publishing uses an ETag
IfMatchcondition. - 429 handling — retry with backoff configured on the client.
- Model hygiene —
typediscriminator +schemaVersionon every document.
The eve agent's semantic memory lives in a separate memory container (src/lib/memory.ts),
queried with Cosmos DB's native vector search. These are the Cosmos DB best practices the
agent kit applies to keep recall fast and cheap:
- Dedicated vector container — memories are isolated from
posts, with their own indexing and TTL policy. /sessionIdpartition key — recall for one conversation is a single-partition query; cross-session recall stays possible but is opt-in.- QuantizedFlat vector index — cosine distance over 1536-dim embeddings (
text-embedding-3-small); quantization keeps the index compact. - Exclude the raw vector from the regular index —
/embedding/*is inexcludedPaths, so the large float array isn't indexed twice (only the vector index uses it). VectorDistance()withORDER BY+ literalTOP— the query orders by similarity and uses a literalTOP k(required for the vector index); the query vector is passed as a parameter, never string-concatenated.kclamped server-side — recall boundskto 1–20 to keep RU/latency predictable.- TTL on memories — the container sets
defaultTtland each memory can carry attlSeconds, so ephemeral context expires automatically instead of growing forever. - Embed once, store the vector — text is embedded on write (
appendMemory) and reused on every read, so recall never re-embeds stored items.
Reference: These follow the official Cosmos DB guidance — Best practices for Azure Cosmos DB for NoSQL, Vector search in Azure Cosmos DB for NoSQL, and Manage indexing policies.
- An Azure Cosmos DB for NoSQL account (create one), or the Cosmos DB Emulator for local dev.
- Node.js 20+.
cp .env.example .env
# then edit .envSet COSMOS_ENDPOINT. For auth, either:
- Recommended: run
az login(or use a managed identity) and leaveCOSMOS_KEYempty — the app usesDefaultAzureCredential. Grant your identity the Cosmos DB Built-in Data Contributor role. - Or: set
COSMOS_KEYto the account key (fine for the emulator / local dev).
Set a strong AUTHOR_API_KEY (e.g. openssl rand -hex 32) so the agent can write.
npm install
npm run seed # creates the database + container and the demo post
npm run dev # http://localhost:3000The agent files live in agent/. To run the interactive agent, initialize eve in this project and start it:
npx eve@latest init . # wires up the eve runtime (keeps the agent/ files)
npm run agent # or: npx eve devMake sure BLOG_API_URL, AUTHOR_API_KEY, and your model key (e.g.
OPENAI_API_KEY) are set in the environment. Then ask the agent to write and
publish a post — it will call the blog's API through its tools.
- Push this repo to GitHub and import it into Vercel.
- In Project → Settings → Environment Variables, set
COSMOS_ENDPOINT,AUTHOR_API_KEY, and either a managed identity (recommended) orCOSMOS_KEY. - Set
BLOG_API_URLto your deployed URL for the agent's environment. - Deploy. Run
npm run seedonce (locally, pointed at the same account) to create the container and seed content.
- Write endpoints require
Authorization: Bearer <AUTHOR_API_KEY>(constant-time compared). Reads are public. - Prefer Microsoft Entra ID over account keys so no secrets sit in your environment.
- Never commit
.env— it is git-ignored.