1:"$Sreact.fragment"
2:I[15579,["/_next/static/chunks/fd0661f1506dcbc6.js","/_next/static/chunks/94245cbda44972fe.js","/_next/static/chunks/af778fff4a0f4be6.js","/_next/static/chunks/b096b037d08e2f31.js"],"Navigation"]
3:I[3013,["/_next/static/chunks/fd0661f1506dcbc6.js","/_next/static/chunks/94245cbda44972fe.js","/_next/static/chunks/af778fff4a0f4be6.js","/_next/static/chunks/b096b037d08e2f31.js"],""]
13:I[41451,["/_next/static/chunks/fd0661f1506dcbc6.js","/_next/static/chunks/94245cbda44972fe.js","/_next/static/chunks/af778fff4a0f4be6.js","/_next/static/chunks/b096b037d08e2f31.js"],"Footer"]
14:I[47913,["/_next/static/chunks/316a3a63422f35de.js"],"OutletBoundary"]
15:"$Sreact.suspense"
:HL["/blog/posts/redis-session-as-shared-state/hero.jpg","image"]
:HL["/blog/posts/logo.png","image"]
0:{"buildId":"TlpKRvbES4zzM7LeczAM7","rsc":["$","$1","c",{"children":[[["$","$L2",null,{}],["$","main",null,{"className":"pt-20 md:pt-24","children":["$","article",null,{"children":[["$","header",null,{"className":"border-b border-border","children":["$","div",null,{"className":"container mx-auto px-6 py-14 md:py-20","children":[["$","$L3",null,{"href":"/blog","className":"mb-8 inline-flex items-center text-sm font-medium text-muted-foreground transition-colors hover:text-foreground","children":[["$","svg",null,{"xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-arrow-left mr-2 h-4 w-4","aria-hidden":"true","children":[["$","path","1l729n",{"d":"m12 19-7-7 7-7"}],["$","path","x3x0zl",{"d":"M19 12H5"}],"$undefined"]}],"Blog"]}],["$","div",null,{"className":"grid gap-8 md:grid-cols-[1fr_0.9fr] md:items-start","children":[["$","div",null,{"className":"max-w-3xl","children":[["$","div",null,{"className":"mb-5 flex flex-wrap items-center gap-3 text-sm text-muted-foreground","children":[["$","span",null,{"className":"rounded-md border border-primary/40 bg-primary/10 px-2.5 py-1 text-primary","children":"Backend"}],["$","span",null,{"children":"2026-W18"}],["$","span",null,{"aria-hidden":"true","children":"/"}],["$","span",null,{"children":"4 min read"}],null]}],["$","h1",null,{"className":"text-4xl font-bold leading-tight text-balance md:text-6xl","children":"Redis Session as Shared State: Coordinating AI Components Without a Message Bus"}],["$","p",null,{"className":"mt-6 text-lg leading-relaxed text-muted-foreground md:text-xl","children":"Two AI processes need to share state for the same session — one primary, one observer running async. Redis with a write-once-per-run convention beats pub/sub: no callbacks, no polling, the consumer decides staleness tolerance."}]]}],["$","div",null,{"className":"overflow-hidden rounded-lg border border-border bg-card","children":["$","div",null,{"className":"relative aspect-[16/9] overflow-hidden","children":[null,["$","img",null,{"src":"/blog/posts/redis-session-as-shared-state/hero.jpg","alt":"A single brass spigot mid-flow over a polished marble basin in a darkened utility room, sharp focus on the water stream, no people, editorial.","className":"h-full w-full object-cover"}],["$","img",null,{"src":"/blog/posts/logo.png","alt":"","aria-hidden":"true","className":"pointer-events-none absolute right-4 top-4 h-[50px] w-[50px] mix-blend-screen"}]]}]}]]}]]}]}],["$","div",null,{"className":"container mx-auto px-6 py-12 md:py-16","children":["$","div",null,{"className":"grid gap-10 lg:grid-cols-[minmax(0,1fr)_280px] lg:items-start","children":[["$","div",null,{"className":"max-w-3xl text-muted-foreground","children":[[["$","p","p-0",{"className":"mt-4 text-base leading-8 md:text-lg first:mt-0 first:text-xl first:leading-relaxed first:text-foreground md:first:text-2xl","children":["$","span","code-0",{"className":"inline-flex w-full max-w-full items-center gap-1 overflow-hidden rounded-md border border-primary/30 bg-secondary px-2 py-0.5 align-middle text-[0.78em] font-medium text-foreground","children":[["$","img",null,{"src":"/blog/posts/logo.png","alt":"","aria-hidden":"true","className":"h-[22px] w-[22px] shrink-0 object-contain"}],["$","span",null,{"className":"overflow-hidden whitespace-nowrap text-clip font-mono","children":"IP/NDA FILTER V.1 \\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"}]]}]}],"\n",["$","h2","h2-0",{"className":"mt-12 text-2xl font-semibold leading-snug text-foreground first:mt-0","children":"The problem"}],"\n",["$","p","p-1",{"className":"mt-4 text-base leading-8 md:text-lg first:mt-0 first:text-xl first:leading-relaxed first:text-foreground md:first:text-2xl","children":"When you have two AI processes that both need to read and write state for the same user session — one acting as the primary interface, one running as a background observer — you need a shared store. The naive approach is to pass state in API calls between them. The problem: the observer runs asynchronously in a thread pool, fires on its own schedule, and the primary process doesn't know when it's done. You can't block on it."}],"\n","$L4","\n","$L5","\n","$L6","\n","$L7","\n","$L8","\n","$L9","\n","$La"],"$Lb",null]}],"$Lc"]}]}]]}]}],"$Ld"],["$Le","$Lf","$L10"],"$L11"]}],"loading":null,"isPartial":false}
4:["$","h2","h2-1",{"className":"mt-12 text-2xl font-semibold leading-snug text-foreground first:mt-0","children":"The approach"}]
5:["$","p","p-2",{"className":"mt-4 text-base leading-8 md:text-lg first:mt-0 first:text-xl first:leading-relaxed first:text-foreground md:first:text-2xl","children":["The solution we're using is Redis as the shared session store, with a write-once-per-run key naming convention. The primary process owns a session dict keyed by ",["$","code","code-0",{"className":"rounded bg-secondary px-1.5 py-0.5 text-[0.9em] text-foreground","children":"session_id"}],". The observer writes two fields into that dict — ",["$","code","code-1",{"className":"rounded bg-secondary px-1.5 py-0.5 text-[0.9em] text-foreground","children":"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"}]," (a JSON blob with ",["$","code","code-2",{"className":"rounded bg-secondary px-1.5 py-0.5 text-[0.9em] text-foreground","children":"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"}],", issues, follow-up suggestions, summary) and ",["$","code","code-3",{"className":"rounded bg-secondary px-1.5 py-0.5 text-[0.9em] text-foreground","children":"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"}]," (a timestamp). Both writes are atomic from the primary process's perspective: it reads the whole session, the observer may or may not have written its fields, and the tool checks for their presence before using them."]}]
6:["$","p","p-3",{"className":"mt-4 text-base leading-8 md:text-lg first:mt-0 first:text-xl first:leading-relaxed first:text-foreground md:first:text-2xl","children":["This avoids all coordination overhead. There's no pub/sub, no callback, no polling. The observer is fire-and-forget: it gets a copy of the transcript, runs its analysis, writes back to Redis, and exits. The next tool call from the primary process picks up whatever is there. If the observer hasn't run yet, the tool returns ",["$","code","code-0",{"className":"rounded bg-secondary px-1.5 py-0.5 text-[0.9em] text-foreground","children":"{\"ready\": false}"}]," and the primary process continues without it."]}]
7:["$","h2","h2-2",{"className":"mt-12 text-2xl font-semibold leading-snug text-foreground first:mt-0","children":"What I learned"}]
8:["$","p","p-4",{"className":"mt-4 text-base leading-8 md:text-lg first:mt-0 first:text-xl first:leading-relaxed first:text-foreground md:first:text-2xl","children":["The interesting edge case is stale observer notes. If a session is long and the observer ran early, the notes may no longer reflect the current conversation state. We handle this with ",["$","code","code-0",{"className":"rounded bg-secondary px-1.5 py-0.5 text-[0.9em] text-foreground","children":"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"}],": the primary process can check how old the notes are and decide whether to trust them. This is a pattern borrowed from cache invalidation — the data isn't wrong, it's just possibly stale, and the consumer decides the tolerance."]}]
9:["$","p","p-5",{"className":"mt-4 text-base leading-8 md:text-lg first:mt-0 first:text-xl first:leading-relaxed first:text-foreground md:first:text-2xl","children":"The other thing: writing partial updates to a Redis JSON blob requires reading the whole object, mutating in Python, and writing back — there's no atomic merge operation unless you use RedisJSON. For session state at this scale (hundreds of concurrent sessions, each a few KB) the read-modify-write cycle is fast enough and the simplicity is worth it. RedisJSON would add a dependency and a new failure mode for marginal gain."}]
a:["$","p","p-6",{"className":"mt-4 text-base leading-8 md:text-lg first:mt-0 first:text-xl first:leading-relaxed first:text-foreground md:first:text-2xl","children":["filter applied by Reel CMO ",["$","a","a-0",{"href":"mailto:reel@bridgestack.systems","children":"reel@bridgestack.systems"}]]}]
b:["$","div",null,{"className":"mt-14 border-t border-border pt-8","children":["$","$L3",null,{"href":"/platform","className":"inline-flex items-center text-sm font-medium text-primary transition-colors hover:text-primary/80","children":["Start a build",["$","svg",null,{"xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-arrow-right ml-2 h-4 w-4","aria-hidden":"true","children":[["$","path","1ays0h",{"d":"M5 12h14"}],["$","path","xquz4c",{"d":"m12 5 7 7-7 7"}],"$undefined"]}]]}]}]
c:["$","aside",null,{"className":"hidden lg:block","children":["$","div",null,{"className":"sticky top-24 space-y-4","children":[["$","h3",null,{"className":"text-xs font-semibold uppercase tracking-wider text-primary","children":["More in ","Backend"]}],["$","div",null,{"className":"space-y-4","children":[["$","$L3","idempotency-scheduled-jobs",{"href":"/blog/idempotency-scheduled-jobs","className":"block overflow-hidden rounded-lg border border-border bg-card transition-colors hover:border-primary/40","children":[["$","div",null,{"className":"relative aspect-[16/9] overflow-hidden bg-secondary/70","children":[["$","img",null,{"src":"/blog/posts/idempotency-scheduled-jobs/hero.jpg","alt":"An industrial timestamp clock on a worn wooden desk with two identical printed slips showing the same stamped time stacked beside it, soft window light, no people.","className":"h-full w-full object-cover","loading":"lazy","decoding":"async"}],["$","img",null,{"src":"/blog/posts/logo.png","alt":"","aria-hidden":"true","className":"pointer-events-none absolute right-2 top-2 h-[24px] w-[24px] mix-blend-screen"}]]}],["$","div",null,{"className":"p-4","children":[["$","p",null,{"className":"text-xs font-medium text-primary","children":"Backend"}],["$","p",null,{"className":"mt-1 text-xs text-muted-foreground","children":["2026-W20"," · ","4 min"]}],["$","p",null,{"className":"mt-2 line-clamp-2 text-sm font-semibold leading-snug text-foreground","children":"Idempotency in Scheduled Jobs — The Restart Problem"}]]}]]}],["$","$L3","pydantic-production-fail-fast",{"href":"/blog/pydantic-production-fail-fast","className":"block overflow-hidden rounded-lg border border-border bg-card transition-colors hover:border-primary/40","children":[["$","div",null,{"className":"relative aspect-[16/9] overflow-hidden bg-secondary/70","children":[["$","img",null,{"src":"/blog/posts/pydantic-production-fail-fast/hero.jpg","alt":"A solid brass padlock with its key still inserted, resting on a worn leather-bound logbook with handwritten entries, soft warm desk light, no people, editorial.","className":"h-full w-full object-cover","loading":"lazy","decoding":"async"}],["$","img",null,{"src":"/blog/posts/logo.png","alt":"","aria-hidden":"true","className":"pointer-events-none absolute right-2 top-2 h-[24px] w-[24px] mix-blend-screen"}]]}],["$","div",null,{"className":"p-4","children":[["$","p",null,{"className":"text-xs font-medium text-primary","children":"Backend"}],["$","p",null,{"className":"mt-1 text-xs text-muted-foreground","children":["2026-W20"," · ","5 min"]}],["$","p",null,{"className":"mt-2 line-clamp-2 text-sm font-semibold leading-snug text-foreground","children":"Fail Fast at the Right Boundary — Pydantic Settings in Production"}]]}]]}],["$","$L3","postgres-notify-ws-delivery",{"href":"/blog/postgres-notify-ws-delivery","className":"block overflow-hidden rounded-lg border border-border bg-card transition-colors hover:border-primary/40","children":[["$","div",null,{"className":"relative aspect-[16/9] overflow-hidden bg-secondary/70","children":[["$","img",null,{"src":"/blog/posts/postgres-notify-ws-delivery/hero.jpg","alt":"Close shot of a server rack with a single blinking amber LED in an otherwise dark row.","className":"h-full w-full object-cover","loading":"lazy","decoding":"async"}],["$","img",null,{"src":"/blog/posts/logo.png","alt":"","aria-hidden":"true","className":"pointer-events-none absolute right-2 top-2 h-[24px] w-[24px] mix-blend-screen"}]]}],["$","div",null,{"className":"p-4","children":[["$","p",null,{"className":"text-xs font-medium text-primary","children":"Backend"}],["$","p",null,{"className":"mt-1 text-xs text-muted-foreground","children":["2026-W18"," · ","3 min"]}],["$","p",null,{"className":"mt-2 line-clamp-2 text-sm font-semibold leading-snug text-foreground","children":"Postgres NOTIFY as a WebSocket Delivery Mechanism"}]]}]]}]]}],["$","$L3",null,{"href":"/blog?category=backend","className":"inline-flex items-center text-sm font-medium text-primary transition-colors hover:text-primary/80","children":["Read all in ","Backend","$L12"]}]]}]}]
d:["$","$L13",null,{}]
e:["$","script","script-0",{"src":"/_next/static/chunks/94245cbda44972fe.js","async":true}]
f:["$","script","script-1",{"src":"/_next/static/chunks/af778fff4a0f4be6.js","async":true}]
10:["$","script","script-2",{"src":"/_next/static/chunks/b096b037d08e2f31.js","async":true}]
11:["$","$L14",null,{"children":["$","$15",null,{"name":"Next.MetadataOutlet","children":"$@16"}]}]
12:["$","svg",null,{"xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":2,"strokeLinecap":"round","strokeLinejoin":"round","className":"lucide lucide-arrow-right ml-2 h-4 w-4","aria-hidden":"true","children":[["$","path","1ays0h",{"d":"M5 12h14"}],["$","path","xquz4c",{"d":"m12 5 7 7-7 7"}],"$undefined"]}]
16:null
