Agent Architecture8 min readยท

Sanity Context vs LangChain Memory: Where Agent State Should Live

Your agent worked in the demo. Then a support conversation ran twenty turns, the model quietly forgot the refund policy it was told at turn two, and it improvised a promise your compliance team never approved.

Your agent worked in the demo. Then a support conversation ran twenty turns, the model quietly forgot the refund policy it was told at turn two, and it improvised a promise your compliance team never approved. The postmortem points at "memory," so someone reaches for LangChain Memory and wires a buffer into the harness. Six weeks later brand voice lives in a Python string, the never-say list is a code comment, and the only person who can change what the agent says is the one engineer who wrote the loop.

The mistake underneath all of this is treating agent state as one thing. It is four things with different lifetimes and different owners: static instructions, per-turn runtime state, retrieved content, and agent-authored notes. LangChain Memory is genuinely good at one of those slices and structurally wrong for the others. Sanity Context is the AI Content Operating System for agent state, an intelligent backend that puts governed instructions and fresh retrieved content where the right people can own them, delivered to any agent loop through the Sanity Context MCP endpoint.

This article maps agent state to its real owners, shows where in-process memory belongs, and gives you a decision framework instead of a religion.

Illustration for Sanity Context vs LangChain Memory: Where Agent State Should Live
Illustration for Sanity Context vs LangChain Memory: Where Agent State Should Live

Agent state is four things, not one bucket

The move most engineers miss is that context is not a single store. It is four kinds of state with different lifetimes, different owners, and different access patterns, and treating them as one is how three of them end up underbuilt. Static instructions are what the agent is: role, voice, escalation rules, the never-say list. Written once, edited rarely, they govern every turn. Per-turn runtime state is who the user is, which page they are on, what is in their cart. It is single-turn and owned by your application. Retrieved content is product catalogs, docs, and policy documents pulled on demand, owned by the content team and stable across sessions. Agent-authored notes are memory and summaries the agent writes for its own future self, cross-session and owned by the agent. Anthropic recently introduced a version of this called "dreaming," where an agent reflects on a session and writes a playbook for the next one.

The taxonomy is not academic. Each kind has different governance. The team that authors brand voice is not the team that writes session-state injection. The compliance officer who signs off on what the agent can say is not the engineer tuning retrieval. If your stack treats all four as one bucket, the wrong people end up making the wrong decisions. LangChain Memory and Sanity Context are not really rivals across all four slices; they are strongest in different quadrants. The whole question of where agent state should live comes down to matching each slice to a home that fits its lifetime and its owner. Get that mapping right and most of the postmortems above never happen. Get it wrong and you rebuild the same string-in-code system every quarter.

Where LangChain Memory genuinely fits

Start with what LangChain Memory does well, because a fair comparison earns the rest of the argument. In the agent harness loop, the wrapper you build around the model, the `history` array is the conversation. It starts with the user's message and grows every turn, and it is the model's only memory between calls. Every turn the model sees the same three things: the system prompt, the whole history so far, and the tool menu. That history slice, the volatile conversation state that has to survive from one call to the next inside a single session, is exactly what LangChain Memory abstractions are built for. Buffer memory, summary memory, and vector-backed memory all address the problem of what to carry forward in the loop without blowing the token budget.

This is real engineering value, and it lives correctly in code. The logic that decides what goes into the context window, and how much, is programmatic. It changes with your model, your latency budget, and your prompt strategy, and it should be reviewed as code, tested as code, and deployed as code. Nobody in Brand or Compliance needs to edit a summarization threshold.

The trouble starts when the same in-process, code-owned pattern gets stretched to cover the other three slices. Static instructions do not belong in a Python string that only engineering can touch. Retrieved content does not belong in a memory object that has no relationship to your source of truth and goes stale the moment someone updates a policy. LangChain Memory is session-scoped and engineering-owned by design. That is a feature for the history slice and a liability everywhere else, because the long-lived slices need editorial review, role-based edit, and scheduled release that an in-process abstraction was never meant to provide.

Static instructions: author like content, gate like code

The application system prompt is customer-facing behavior, and it should be governed like it. The real choice is not "content, loose" versus "code, rigorous." It is "governed, with the right people able to edit and a test gate on the way out" versus "a string only engineering can touch." The principle is to author it like content and gate it like code: versioning, review, an eval gate, scheduled release, and rollback, separated from your code deploys.

This is where Sanity Context stops being a retrieval tool and becomes the home for the static-instruction slice. In the Studio the prompt is a document split into fields, and splitting it is not cosmetic, it is access control. You define a `voice` field owned by Brand, a `userContext` field owned by Product that governs how the agent uses per-turn state like tier or cart, an `escalation` field owned by Support for when to hand off to a human, and a `mustNotSay` field owned by Compliance for topics the agent must refuse. None of those people files a pull request, and the fields stitch into one runtime prompt at request time. Because it is content in the Studio, you get real-time collaboration, version history, scheduled publishing, and rollback for free, and you can stage new agent behavior through Content Releases the same way you stage a website.

This is the concrete difference in ownership. Vipps came to Sanity wanting the whole organization to contribute to prompt writing, with product managers owning it, not just engineers. You cannot satisfy that requirement with an in-process memory string, no matter how clean the abstraction. LangChain Memory has no built-in editorial review, no role-based edit, and no scheduled release, because it was never trying to solve for non-engineer ownership. It solves the history slice; this slice needs a different home.

Retrieved content: freshness is the whole game

The retrieved-content slice, product catalogs, docs, and policy documents pulled on demand, is where the vector-database question usually enters, and where the hidden cost lives. Pure vector search ranks by semantic similarity, which is excellent for intent but blind to hard predicates. Pure keyword search, classic BM25, nails exact terms and misses paraphrase. Anthropic's own work shows hybrid retrieval, blending both, meaningfully cuts retrieval failures over either alone. The catch is that a bolt-on vector database does not own your source content, so keeping embeddings and the index fresh on every update becomes a permanent incremental-indexing, re-embedding, and deletion-handling project.

Sanity Context does hybrid retrieval in a single GROQ query against Content Lake. Structured predicates do the filtering that has to hold, and then a `score()` pipeline blends a BM25 keyword match on the title, weighted 2x with `boost([title] match text::query($queryText), 2)`, against `text::semanticSimilarity($queryText)` across the document, ordered by `_score desc`. One query, one place, and the intent-plus-constraint search that vector-only stacks assemble from parts. Because embeddings are tied to content in Content Lake rather than living in a separate store, an edit propagates without a second pipeline to babysit.

That is the structural line between the two approaches. With a separate vector DB plus glue code, freshness becomes a line item that never leaves your roadmap. When the content store and the search index are the same system, freshness is handled for you. LangChain Memory is not in this contest at all: it is not a content system, and stuffing your catalog into a memory object gives you a stale copy with no governance. The right pairing is LangChain Memory for the volatile history slice and Sanity Context for retrieved content, each doing the job it was built for.

The context window is a budget, not a bucket

There is a tempting shortcut that both memory abstractions and naive retrieval encourage: just tell the model everything. The context window keeps growing, so why not fill it? Because even a million-token window does not mean you should fill it. Too much context leads to semantic collapse and misdirection, where the model loses track of what it is doing, and it is slow and expensive for you and your customers. Overfilling is not a safe default; it is a failure mode with a bill attached.

This reframes what "memory" is for. The goal is not to remember everything, it is to hand the model the smallest set of tokens that lets it act correctly this turn. That is a curation problem, and it splits cleanly across the two systems. On the history side, this is exactly the discipline LangChain Memory encodes: summarize, window, and prune so the conversation does not grow unbounded. On the retrieved-content side, it is why precise hybrid retrieval matters more than raw recall. A query that returns the three right documents beats one that dumps thirty, because the extra twenty-seven are context rot.

Sanity Context helps on the retrieval side by making the query do the narrowing. Structured predicates in GROQ eliminate whole classes of irrelevant results before ranking even runs, so what reaches the context window is already filtered to what has to hold true. You are not asking the model to ignore noise; you are not sending the noise. The lesson for the architecture is to treat every slice as a budget line. Give the history slice a disciplined in-process manager, give the retrieved-content slice precise governed queries, and stop confusing a large window with a license to overpack it.

Sanity Context is a product, not just an endpoint

A common misread is that Sanity Context is "just an MCP server" you point an agent at. Sanity Context is the product: a way to give agents structured, governed access to your content. The Context MCP is one surface of it, a hosted read-only endpoint that any agent loop can connect to. The mental model to keep is that Sanity Context has an MCP, a knowledge base, and an ingest path. It is not only an MCP.

That distinction matters for the state question. The MCP endpoint is how a production agent, including one whose harness uses LangChain Memory for its conversation history, actually queries the governed instructions and retrieved content at runtime. The ingest path and Knowledge Bases are how the content that feeds retrieval gets there in the first place, turning datasets, sites, and support documents into agent-readable material on the same retrieval path. Studio and Content Releases are where the humans govern and stage all of it. Read together, these surfaces are why Sanity Context sits under the "Power anything" and "Automate everything" pillars: it operates content end to end for agents rather than stopping at publishing.

This is also the honest boundary of the comparison. LangChain Memory and Sanity Context are not competing to be the same box. LangChain Memory is an in-process library for the conversation slice inside your harness. Sanity Context is the intelligent backend for the three long-lived, multi-owner slices, delivered to that harness over an endpoint. On Sanity's compliance posture, Context runs on infrastructure with SOC 2 Type II, GDPR alignment, regional hosting and data residency, and a published sub-processor list, which is the kind of governance a `mustNotSay` list in a code comment cannot claim. The architectures are complementary, not interchangeable, and treating them as either/or is how teams end up rebuilding half of one inside the other.

A decision framework for where each slice lives

Skip the platform loyalty and route by slice. First, the conversation history: the volatile, single-session state that is the model's only memory between calls. Keep it in-process. LangChain Memory, or your own harness logic, is the right home, because the decisions here are programmatic and change with your model and latency budget. This is code, and it should stay code.

Second, static instructions: role, voice, escalation, and the never-say list. Ask who needs to edit these. If the answer includes anyone in Brand, Product, Support, or Compliance, and it always does, they cannot live in a string. Put them in the Studio as a fielded prompt document with role-based ownership, an eval gate, scheduled release, and rollback, and deliver them at runtime through the Sanity Context MCP endpoint. Third, retrieved content: catalogs, docs, and policies. Ask whether it changes and who owns the source. If it changes and the content team owns it, native hybrid retrieval over Content Lake keeps the index fresh without a separate re-embedding pipeline, which is the difference between freshness being solved and freshness being a standing roadmap item.

Fourth, agent-authored notes: cross-session memory the agent writes for itself. This is the newest and least settled slice; treat it as its own store with its own retention and review policy rather than smuggling it into either of the others. The test for the whole design is simple: for each slice, name the owner and name the review gate. If a slice's owner is "whoever last edited the harness" and its review gate is "the PR that shipped," you have found the thing that will cause your next production incident. The right answer is almost never one tool. It is LangChain Memory doing the loop and Sanity Context governing everything with a longer lifetime than a single session.

Where each slice of agent state should live: Sanity Context vs. common alternatives

FeatureSanityLangChain MemoryPinecone + glueSierra AI / Decagon
Conversation history (single session)Not its job; the harness owns the loop and connects to Context over the MCP endpoint for the longer-lived slices.Strong fit: buffer, summary, and vector-backed memory manage the history array in-process where it belongs.Not designed for volatile per-turn history; it is a vector store for retrieved content, not loop state.Handled inside the vendor's own harness, which you do not control or inspect directly.
Static instructions (voice, escalation, never-say)Fielded prompt document in the Studio: voice, userContext, escalation, and mustNotSay owned by different teams, stitched at runtime.Lives as an engineering-owned string in code; no built-in role-based edit or non-engineer review.Out of scope; a vector DB does not model or govern instruction fields.Configured in the vendor console; ownership and review depth vary and are not in your Studio.
Retrieved content freshnessEmbeddings tied to content in Content Lake, so edits propagate without a separate re-embedding pipeline to maintain.No relationship to a source of truth; content stuffed into memory becomes a stale copy.Does not own the source, so incremental indexing, re-embedding, and deletions are a standing project.Often crawls or ingests a copy of your data, so freshness depends on the vendor's re-crawl cadence.
Hybrid retrieval (keyword + semantic)Native in one GROQ query: boost([title] match text::query($queryText), 2) blended with text::semanticSimilarity() via score().Vector-backed memory is semantic only; keyword precision and hard predicates are not the design goal.Strong semantic ranking, but structured predicates need a separate metadata filter layer assembled by you.Retrieval is a black box; you tune inputs, not the blend of keyword and semantic scoring.
Governance: review, versioning, rollbackVersion history, real-time collaboration, scheduled publishing via Content Releases, and rollback come with the Studio.Governed only as far as your code review governs it; no editorial gate for non-engineers.Index operations are code; content governance sits in whatever system feeds it, not in Pinecone.Depends on the vendor's console features; staging and rollback are not yours to define.
Compliance postureSOC 2 Type II, GDPR alignment, regional hosting and data residency, and a published sub-processor list.A library, not a hosted service; compliance is whatever your own deployment provides.Managed service with its own certifications; content governance and residency still your responsibility.Vendor-dependent; verify certifications and where your ingested copy is stored.
Ownership and lock-inYou own the content store, the queries, and the instruction documents; agents connect via a read-only MCP endpoint.You own the code and state, but long-lived slices become engineering-only by default.You own vectors and glue; the glue to keep it fresh is code you maintain indefinitely.Customers question total cost versus building and owning the agent, since data lives in the vendor's tool.