BlogWriter uses the Responses API which can help with state maintenance. BlogWriter, however, implements important state (context) using a session object, and manages long-term memory by writing to a Cosmos Database.

I’m reusing this image because (a) it is apt and (b) he’s so cute!
You can see that we are using the Responses API in the Researcher agent where the AI agent is reused but is stateless across turns. ResearcherAgent.cs specifically says that “no agentSession is retained.”
What is stateful:
- Application session state
Stored inBlogSession.Stateas aResearchStateinBlogSession.cs. This is the real durable state. - ResearchState fields
Held inResearchState.cs:MainTaskMinWordsMaxWordsResearchFindingsSearchRefinementsDraftReviewNotesRevisionNumberNextStepCurrentSubTask
- Persistence
Sessions are saved to Cosmos DB inCosmosBlogSessionStore.cs. Cosmos also tracksOwnerId,CreatedAt,UpdatedAt, andETag. - Per-process token budget
TokenCapChatClienttracks cumulative input, output, reasoning, and total tokens across model calls inTokenCapChatClient.cs. That is stateful, but it is spend-control state, not conversation memory.
What is not being held:
- No persisted Responses API
response_id - No retained MAF
AgentSession - No explicit conversation/thread object passed between turns
- No server-side conversation history intentionally reused by this app
Each agent call builds a fresh message from the current ResearchState. For example, the Author call includes the main task, research findings, current draft, review notes, follow-up, and target word count in AuthorAgent.cs. So continuity comes from app state, not from the Responses API maintaining the conversation.
There may be transient state inside a single Responses run, especially tool-call state for the Researcher’s hosted web search, but once that run returns, the durable continuity is whatever the app writes back into ResearchState.
Storing in Cosmos
State is saved as one Cosmos DB document per blog session.
The shape is defined by BlogSession.cs: each document contains:
{
"id": "<session-guid>",
"OwnerId": "<current-user-owner-id>",
"CreatedAt": "...",
"UpdatedAt": "...",
"State": {
"MainTask": "...",
"MinWords": 1000,
"MaxWords": 2000,
"ResearchFindings": [],
"SearchRefinements": [],
"Draft": "...",
"ReviewNotes": "...",
"RevisionNumber": 1,
"NextStep": "...",
"CurrentSubTask": "..."
}
}
The actual save logic is in CosmosBlogSessionStore.cs:
CreateAsync(...)creates a newBlogSession, assigns a GUIDid, gets the currentOwnerId, sets timestamps, and callsCreateItemAsync(...).- The Cosmos partition key is
OwnerId, so each user’s sessions are partitioned by owner. SaveAsync(...)updatesUpdatedAtand callsReplaceItemAsync(...), replacing the whole session document.- It uses the session’s
ETagwithIfMatchEtag, so concurrent stale saves are rejected. - If Cosmos returns
PreconditionFailed, the app throwsSessionConflictException.
The ETag itself is not stored inside the document because BlogSession.ETag has [JsonIgnore]; it is only kept in memory after reads/writes for optimistic concurrency.
So: the app does not store chat history as separate Cosmos rows. It stores the full workflow state as a nested State object inside the session document, then replaces that document after each completed workflow run or revision.
Note: The initial draft of this post was written by BlogWriter and then edited by Jesse Liberty.
Illustrations by Copilot. Caution: LLMs make mistakes; this post is offered as is.
