
Sharing State Across Sessions and Agents: Memory, MCP, and Handoffs in .NET
Agents forget everything between sessions and cannot see each other's work by default. This third part of Agentic Engineering for .NET Teams covers the practical patterns: persistent memory, MCP servers, multi agent coordination, and the C# wiring that makes them work together.
The Problem: Agents That Forget and Cannot See Each Other
If you followed Part 2 of this series, you know how much effort goes into getting the right context into one agent's window. Now run two agents in parallel and ask one what the other found. It has no idea, because each one has its own context and nothing connects them unless you build that connection.
The protocol layer has moved in the same direction. The MCP 2026-07-28 specification removes the initialize handshake (SEP-2575) and protocol-level sessions, including the Mcp-Session-Id header (SEP-2567). Every request now carries its own protocol version and client capabilities, so each HTTP request is independent and ordinary round-robin load balancing works without sticky sessions. A stateless protocol does not make your application stateless, though. Servers that need state across calls now pass explicit handles as ordinary tool arguments, and remembering things becomes the job of your code.
On the Anthropic side, Claude Managed Agents runs each agent in its own session thread, a context-isolated event stream with its own conversation history. The agents in a session share a sandbox and its filesystem, but not their conversations. The coordinator knows what it sent each subagent; it only knows what they found when they report back or write it somewhere it can read.
Three patterns address this:
- Persistent memory. Store state where any agent can read and write it.
- Model Context Protocol (MCP). Expose that state and your internal tooling through a standard interface any agent can call.
- Structured handoffs. Pass tasks and results across an agent boundary in a way that preserves context.
Persistent Memory: What to Store and What to Discard
Claude Code's agent teams show how far a simple shared store can go. The feature is experimental and off by default (you turn it on with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1). One session is the lead; it creates a shared task list, stored on disk under ~/.claude/tasks/{team-name}/. Tasks are pending, in progress, or completed, and a task that depends on others stays blocked until they are completed. Teammates claim the next unblocked task themselves, and Claude Code uses file locking so two teammates cannot claim the same task. Teammates can also message each other directly through per-agent mailbox files.
The task-list part is easy to build in C#. Here is a minimal version backed by a JSON file:
using System.Text.Json;
public record AgentTask(
string Id,
string Description,
string[] DependsOn,
string Status, // pending | in-progress | complete | failed
string? ClaimedBy,
string? Result,
DateTimeOffset UpdatedAt
);
// One process, one lock: safe for a single server instance only.
public sealed class FileTaskStore(string path)
{
private static readonly SemaphoreSlim _lock = new(1, 1);
public async Task<AgentTask?> ClaimNextAsync(string agentId)
{
await _lock.WaitAsync();
try
{
var tasks = await ReadAsync();
var completedIds = tasks.Where(t => t.Status == "complete")
.Select(t => t.Id)
.ToHashSet();
var next = tasks.FirstOrDefault(t =>
t.Status == "pending" &&
t.DependsOn.All(dep => completedIds.Contains(dep)));
if (next is null) return null;
var claimed = next with
{
Status = "in-progress",
ClaimedBy = agentId,
UpdatedAt = DateTimeOffset.UtcNow
};
await WriteAsync(tasks.Select(t => t.Id == next.Id ? claimed : t));
return claimed;
}
finally { _lock.Release(); }
}
public async Task CompleteAsync(string taskId, string result)
{
await _lock.WaitAsync();
try
{
var tasks = await ReadAsync();
await WriteAsync(tasks.Select(t => t.Id == taskId
? t with { Status = "complete", Result = result, UpdatedAt = DateTimeOffset.UtcNow }
: t));
}
finally { _lock.Release(); }
}
private async Task<List<AgentTask>> ReadAsync() =>
JsonSerializer.Deserialize<List<AgentTask>>(
await File.ReadAllTextAsync(path)) ?? [];
private Task WriteAsync(IEnumerable<AgentTask> tasks) =>
File.WriteAllTextAsync(path,
JsonSerializer.Serialize(tasks, new JsonSerializerOptions { WriteIndented = true }));
}ClaimedBy and UpdatedAt are there so you can see who holds a task and how old an entry is. The SemaphoreSlim only protects the file inside one process, which matters in the next section.
What belongs in persistent memory: summaries of findings, intermediate artefacts (generated code, analysis reports), task status, and links to authoritative sources. What does not belong: raw conversation turns (they bloat the file and age badly), credentials, or anything an agent should re-derive fresh each run. Keep entries time-stamped so you can judge freshness before injecting them into a new session.
Model Context Protocol: A Standard Bridge to Your Systems
MCP gives any agent, whatever the vendor or runtime, a standard way to call your tools and read your data. The official C# SDK (ModelContextProtocol) shipped 2.0 on 28 July 2026, the same day as the new spec, and the current release is 2.3.0. It targets .NET 8, .NET 9, .NET 10 and netstandard2.0.
Install it:
dotnet add package ModelContextProtocol.AspNetCoreHere is a minimal ASP.NET Core MCP server that exposes the task store as two tools. Since SDK 2.0, HttpServerTransportOptions.Stateless defaults to true for the 2026-07-28 protocol revision, so the endpoint keeps no session between requests:
using System.ComponentModel;
using ModelContextProtocol.Server;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(
new FileTaskStore(builder.Configuration["TaskFile"] ?? "tasks.json"));
builder.Services
.AddMcpServer()
.WithHttpTransport() // stateless by default since SDK 2.0
.WithTools<TaskTools>();
var app = builder.Build();
app.MapMcp("/mcp");
app.Run();
[McpServerToolType]
public sealed class TaskTools(FileTaskStore store)
{
[McpServerTool(Name = "claim_task")]
[Description("Claim the next pending task whose dependencies are complete.")]
public Task<AgentTask?> ClaimTask(
[Description("Id of the agent claiming the task")] string agentId) =>
store.ClaimNextAsync(agentId);
[McpServerTool(Name = "complete_task")]
[Description("Mark a task complete and store its result.")]
public async Task<string> CompleteTask(string taskId, string result)
{
await store.CompleteAsync(taskId, result);
return "ok";
}
}The SDK builds each tool's input schema from the method signature (claim_task takes a required agentId string) and returns the AgentTask as camelCase JSON, or null when nothing is claimable. The tool class gets FileTaskStore through constructor injection.
Point your agents at http://your-mcp-host/mcp in their tool configuration and they coordinate through the store without knowing about each other. The same server can front any shared resource, a SQL database, an internal API, a documentation index, through one protocol that Claude, GitHub Copilot and other MCP clients speak.
One caution before you put this behind a load balancer. The protocol no longer needs session affinity, but this store does: its lock lives in one process. We ran two instances of this exact server against one task file, with 30 concurrent claims split between them, ten times. Every run failed at least one call on a half-written file. A single instance handed out 30 tasks to 30 concurrent callers without a duplicate. For more than one instance, move the store into a database and claim rows atomically, for example with PostgreSQL's SELECT ... FOR UPDATE SKIP LOCKED.
Multi-Agent Coordination: Subagents, Handoffs, and the Shared Bus
Two topologies matter in practice:
Hierarchical, or subagent, model. One coordinator dispatches tasks to specialised subagents, collects results, and synthesises. Claude Managed Agents makes the threads explicit: a subagent's thread persists until it is archived, so the coordinator can send it a follow-up and the subagent still has everything from its earlier turns. Delegation there is one level deep, with at most 25 child threads per session. Anthropic's own research system used Claude Opus 4 as the lead and Claude Sonnet 4 as subagents, and it outperformed single-agent Claude Opus 4 by 90.2% on their internal research evaluation.
Team model. A group of agents that pick up work from a shared list and talk to each other directly. Claude Code's agent teams work this way: the lead creates the task list, teammates claim unblocked tasks on their own and message each other by name, and the lead synthesises at the end instead of routing every step.
For .NET teams, Microsoft Agent Framework 1.0 (generally available since 3 April 2026), the successor to Semantic Kernel and AutoGen, is the native option. It supports both topologies and can use MCP servers as tool sources.
Handoffs in the OpenAI Agents SDK follow a transfer-tool pattern: agent A calls a transfer_to_<agent> tool, and control moves to agent B along with the conversation history. That is clean for sequential pipelines but needs extra work when you want parallel coordination or a shared stream of results.
The 2026-07-28 spec also adds Multi Round-Trip Requests (MRTR, SEP-2322). Instead of the server sending its own requests for sampling or elicitation in the middle of a call, it returns an InputRequiredResult listing what it needs, and the client retries the original request with inputResponses. Interactive tools no longer need a long-lived connection, which fits the request-per-call model of ASP.NET Core well.
Pitfalls to Anticipate
Stale memory is worse than no memory. An agent injecting a six-week-old analysis into its context will act on it confidently. Timestamp every entry and require agents to check freshness before relying on cached findings. Treat memory like a cache, not a database of truth.
Context bleed between tasks. When an agent completes task A and immediately picks up task B, framing from A can shape how it approaches B. An explicit reset, an instruction that describes task B from scratch, is more reliable than hoping the model reframes itself.
Securing shared tools. An MCP server exposed to multiple agents is a shared attack surface. Scope tools to specific agent identities where your infrastructure allows, and do not expose broad write permissions through a tool that subagents can call without coordinator approval. The 2026-07-28 spec tightens authorization in concrete ways: clients must validate the iss parameter against the recorded issuer (RFC 9207, SEP-2468), must key stored client credentials by the authorization server that issued them (SEP-2352), and Dynamic Client Registration is now deprecated in favour of Client ID Metadata Documents.
Two Anthropic beta headers, and they don't stack. If you call Claude Managed Agents directly from .NET, agent and session endpoints need managed-agents-2026-04-01. Memory store endpoints use agent-memory-2026-07-22 instead, and sending both headers on a memory store call returns a 400. Attaching a memory store to a session is a session call, so it still uses managed-agents-2026-04-01.
Humans must stay in the loop. Agent teams that distribute tasks among themselves are efficient and hard to interrupt. Define explicit pause points, stages where the coordinator shows a summary and waits for human confirmation before the next wave of tasks starts. For production systems this is a requirement, not a nice-to-have.
Where This Fits in the Series
Part 1 set up the agentic workflow and Part 2 covered getting the right context into a single agent. This part handles the state and coordination layer that makes multi-session, multi-agent work coherent. Part 4 goes one level down, to the function-calling loop that every tool call in this article runs on.
A stateless protocol is the right call for scalability, and it moves the memory problem into your code. A small MCP server in front of a shared store, a JSON file for a prototype on one instance and a database once you run more than one, is a practical answer for .NET teams today. Keep the state surface small, timestamp everything, and put a human checkpoint before agents act on what they remember.
Sources
- Announcing v2.0 of the official MCP C# SDK - .NET Blog
- Beta SDKs for the 2026-07-28 MCP Spec Release Candidate Are Here | Model Context Protocol Blog
- The 2026-07-28 MCP Specification Release Candidate | Model Context Protocol Blog
- C# MCP SDK - Official .NET SDK for Model Context Protocol | a2a mcp
- Developer's Guide to the Model Context Protocol (MCP) 2026 | Nerd Level Tech
- Model Context Protocol Specification Version Timeline - Version-by-Version Changes and Adoption Milestones | hidekazu-konishi.com
- # Research Report: Model Context Protocol (MCP) Specification - January 2026
- Multiagent orchestration - Claude Platform Docs
Keep reading

October 29, 2026 · 7 min
Context Engineering: Getting the Right Information to Your .NET Coding Agent
An AI coding agent is only as good as the context it sees. Context engineering, the discipline of deliberately curating what your agent reads, is the difference between an assistant that rewrites your service layer correctly and one that confidently breaks it.
Read
October 29, 2026 · 8 min
Claude Code for .NET Developers: A Practical Agentic Workflow
Claude Code is a terminal first AI agent that reads, edits and runs your .NET codebase on its own. Here is how to set it up, drive it well, and keep it safe in a team.
Read
September 19, 2026 · 6 min
Building an Agentic System in .NET, Part 2: Writing an MCP Server in C#
How to expose your own tools to any AI agent by building a Model Context Protocol server in C# with the official ModelContextProtocol SDK v2.2.0. Tool design, input validation, context window budgeting, and the naming mistakes that make models misuse your tools silently.
Read