← back/blog/typed-context

the Room Protocol · typed context

Typed context: structured artifacts over prose and vector search

The difference between agents that coordinate and agents that talk past each other comes down to one choice: is the shared knowledge structured when it's written, or reconstructed when it's read?

10 min read·shreyaspadmakiran.com

The whole premise of the Room Protocol is that agents coordinate through shared state instead of messages. But “shared state” is doing a lot of hidden work in that sentence. If the state is just a pile of text notes agents leave for each other, you haven't escaped messaging — you've just moved the chat into a database. The escape only happens if the knowledge is structured. That's what the context store is, and this post is about why it's typed and what that buys over the alternatives.

Act I — Prose is a message in disguise

The handoff that has to be exact

Here's the canonical moment. One agent builds a payments service. Another builds the billing job that reconciles charges against it, and it needs the contract: the endpoints, methods, request and response shapes, whether auth is required. If the first agent leaves a note like “the charge endpoint takes an amount and a card and tells you whether it went through”, the billing agent now has to parse English to recover a precise interface. Does the endpoint live at /charge or /payments/charge? Is the amount in dollars or cents? Is the field status or outcome, and what does a declined card look like? The prose doesn't say, so the consumer guesses, and a guess in an API contract is a bug with a delay on it.

This is the same lossy, order-dependent, interpretation-heavy channel that made agent-to-agent chat a bad idea in the first place. Free-text context is a message wearing a database's clothes.

Typed at the point of writing

The fix is that every context entry has a type from a closed set, and the producer fills a payload with a known shape for that type. The five types cover what agents actually need to hand each other:

type ContextType =
  | "api_contract"    // an interface one agent builds and another consumes
  | "arch_decision"   // a decision + rationale + alternatives + consequences
  | "task"            // acceptance criteria and technical notes for a task
  | "change_request"  // "I need X from you", with urgency and blocking task
  | "note";           // free-form, the deliberate escape hatch

An api_contractisn't a paragraph; it's a structure the consumer can rely on without reading prose:

interface ApiContractPayload {
  service: string;
  version: string;
  endpoints: {
    method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
    path: string;
    request?: Record<string, unknown>;
    response: Record<string, unknown>;
    auth_required: boolean;
    description: string;
  }[];
  base_url?: string;
}

Now the billing agent doesn't interpret anything. It reads a list of endpoints, each with a method and a path and a response shape. The knowledge was made precise at the moment it was written, by the agent that actually had the information, instead of being reconstructed later by the agent that didn't. That inversion — structure at write time, not parse time — is the entire idea.

Free-text context versus typed contextLeft: a prose note flows into a question mark at the consumer. Right: a typed api_contract flows into a clean checkmark at the consumer.free-text note“charge takes an amountand a card, tells youif it went through”?consumer must guesstyped api_contractPOST /v1/payments/chargereq {amount_cents, source}res {charge_id, status}consumer relies on shape
Same information, two representations. Only one of them lets the consumer build against it without a round of clarification.
“Just use structure” sounds obvious until you notice the two more fashionable options — throw it in a chat, or throw it in a vector store — and why neither is right for this.
Act II — Why not the fashionable options

Why not just let them chat?

The chat approach is the one everyone reaches for because agents are good at language. But language is exactly the problem. A conversation is transient, order-dependent, and has to be interpreted every time it's read. There's no guarantee two readings produce the same understanding, no way to query “what's the current auth contract” without re-reading a thread, and no shape to rely on. It optimizes for the wrong thing: expressiveness, when what a handoff needs is precision.

Why not a vector store?

The other reflex in 2026 is: it's knowledge, so embed it and retrieve it semantically. This is the more interesting wrong answer. Vector search is fuzzy by design— it returns what's probably relevant, ranked by similarity. That's exactly what you want for “find me docs about authentication” and exactly what you don't want for “give me the one current auth contract I must implement byte-for-byte.” You don't want the probably-relevant contract. You want the contract, by id, deterministically.

Structured retrieval beats semantic retrieval whenever correctness is non-negotiable, and an interface an agent is about to code against is the definition of non-negotiable. Context here is fetched by type and by id, not by cosine similarity:

list_context({ roomId, type: "api_contract" })   // all contracts, exact
read_context({ roomId, id })                     // this one, deterministic

Semantic search over context is a genuinely useful later feature for discovery when a room has hundreds of entries. But it belongs on top of the exact store, not in place of it. The base layer has to be deterministic.

Why not a full relational schema per type?

The opposite overreach: give every context type its own table with typed columns and constraints. Too rigid, and it drags in the whole migration-and-Postgres cost the architecture deliberately avoids. Agents produce artifacts whose inner shapes evolve; a discriminated union with a typed envelope and a JSON payload is the sweet spot. The envelope — id, type, author, timestamp, consuming agents, version — is fixed and reliable; the payload is structured by convention per type. You get the shape guarantees where they matter without a migration every time a payload gains a field.

Act III — What the envelope unlocks

The metadata earns its keep

Because every entry carries the same envelope, two useful behaviors come almost for free. The consuming_agents field lets a write notify exactly the agents that depend on an artifact, instead of broadcasting to the whole room — so writing a contract can auto-post an event straight to the billing agent that was waiting on it. And because author and timestamp are always present, the context store doubles as an auditable decision record: every architecture decision, with its rationale and rejected alternatives, is queryable after the fact.

That decision record turns out to be a real asset. A team coordinating several projects gets a queryable trail of why every interface and decision looks the way it does, which is what makes onboarding a new agent (or a new human) fast and makes handoffs trustworthy. Reliability of handoffs is the thing you are ultimately selling, and the typed store is what makes it structural rather than hopeful.

What this version doesn't enforce yet

The honest edge: the type is validated, but the per-type payload shape is currently a documented convention, not a schema the server rejects on. A malformed api_contractpayload would be stored. Tightening that into real per-type validation is a clear next step and a small one, since the schemas already exist as interfaces. There's also no version history yet — updating a contract writes a new entry rather than appending to a tracked lineage — and no semantic layer on top for discovery. None of these undercut the core claim; they're the difference between “typed by convention” and “typed by enforcement.”

But the direction is the whole point. The instinct with LLM agents is to lean on their fluency and let everything be text, because they're so good at text. The Room Protocol bets the other way: the more capable the agents, the more it matters that the things they hand each other are precise. You don't coordinate a build on vibes and paragraphs. You coordinate it on contracts.

— shreyas

more in this series

01Coordinating agents through shared state · the idea

02Concurrency control for a shared plan · locks and cursors

03A stateless server on a single Redis · architecture

04Typed context over prose and vector search · you are here

05MCP as transport · protocol layer

06Multi-tenancy on one Redis · isolation and access