Skip to content

Persistence

FabrCore 2.0 · Release and package availability

These guides track the current 2.0 source. Stable 2.0.0 publication is pending; package commands show the release target. Until it is published, follow the source quick start or use a matching available prerelease set. Release migration · Runtime modes

Default standalone Orleans state is in memory and lost on restart. SQL mode supplies durable Orleans defaults; explicit Azure/custom storage remains supported. ACL, Memory, GraphRAG and operational stores have their own SQL schemas.

Persistence

FabrCore provides built-in persistence through Orleans grain state, enabling conversation history and custom data across activations. Restart durability requires a persistent provider; default standalone storage is in memory.

FabrCoreChatHistoryProvider

FabrCoreChatHistoryProvider implements the framework history-provider integration with Orleans-backed persistence. It's automatically wired when using CreateChatClientAgent.

Buffered Writes

Messages are held in memory until flush — fast, no I/O per message.

Lazy Loading

History is loaded from Orleans state on first access, not on initialization.

Automatic Persistence (Recommended)

PersistentAgent.cs
using FabrCore.Core;
using FabrCore.Sdk;
using Microsoft.Agents.AI;

[AgentAlias("persistent-agent")]
public sealed class PersistentAgent(
    AgentConfiguration config, IServiceProvider services, IFabrCoreAgentHost host)
    : FabrCoreAgentProxy(config, services, host)
{
    private ChatClientAgentResult chat = null!;
    public override async Task OnInitialize()
        => chat = await CreateChatClientAgent("default", "main");
    public override async Task<AgentMessage> OnMessage(AgentMessage message)
    {
        var result = await chat.Agent.RunAsync(message.Message ?? "", chat.Session);
        var response = message.Response();
        response.Message = result.Text;
        return response;
    }
}
Auto-Flush

After each OnMessage completes, the Orleans grain automatically calls FlushAsync() on all tracked stores. On grain deactivation, any remaining pending messages are flushed during graceful deactivation; abrupt termination can still lose unflushed data.

Thread ID Strategies

StrategyThread IDUse Case
One per agentconfig.HandlePersonal assistant, single-user agents
Per-usermessage.FromHandleMulti-user agents
Per-channelmessage.Channel ?? "default"Channel-based chat
Combined$"{fromHandle}:{channel}"User-specific within channels

API Reference

MethodDescription
AddMessagesAsync(messages)Add messages to in-memory buffer (fast, no I/O)
GetMessagesAsync()Get all messages (persisted + pending), lazy-loads from Orleans
FlushAsync()Persist pending messages to Orleans grain state
HasPendingMessagesReturns true if there are unsaved messages
ThreadIdThe unique thread identifier

Custom State

Persist arbitrary typed data (user info, counters, preferences) that survives grain deactivation:

MethodDescription
GetStateAsync<T>(key)Get a strongly-typed value by key
GetStateOrCreateAsync<T>(key, factory)Get existing value or create with factory
HasStateAsync(key)Check if a key exists
SetState<T>(key, value)Set a value (buffered until flush)
RemoveState(key)Remove a key (buffered until flush)
FlushStateAsync()Persist all pending changes to Orleans
Custom State Example
public override async Task<AgentMessage> OnMessage(AgentMessage message)
{
    var response = message.Response();

    // Get or create pattern
    var stats = await GetStateOrCreateAsync(
        "stats", () => new ConversationStats());
    stats.MessageCount++;
    stats.LastMessage = DateTime.UtcNow;
    SetState("stats", stats);

    // Process message...
    var result = await agent!.RunAsync(message.Message, thread);
    response.Message = result.Text;

    await FlushStateAsync();
    return response;
}
Best Practice

Group related state updates and call FlushStateAsync() once at the end of OnMessage. On grain deactivation, pending changes are auto-flushed during graceful shutdown; failures must still be handled.

Validated history compaction

2.0 separates per-model-call working context from durable conversation history. Older tool results become bounded excerpts at 50% and 80% of the input working set; full originals remain in stored history until durable compaction.

Automatic durable compaction uses 70% of a usable working set, with a 75% fallback. It preserves instructions, the latest user message and latest interaction group in a single validated write. Invalid or stale summary results leave original history intact. See context and history compaction for metadata, overrides and safeguards.

Orleans Storage Providers

Message threads and custom state are stored in Orleans grain state. Supported providers:

  • In-memory — Development only, data lost on restart (built into FabrCore.Host)
  • SQL Server — Production, durable, with automatic table deployment (FabrCore.Host package)
  • Azure Storage — Cloud-native; grain state in Blob Storage by default (subject to Azure Blob and serializer limits), Table Storage opt-in (FabrCore.Host.AzureStorage package)
  • Custom Orleans providers — PostgreSQL, MySQL, Cosmos DB, etc. via the advanced Orleans path or a custom IFabrCoreOrleansProvider

SQL Server is integrated in Host; Azure uses its separate provider. The feature database enables SQL defaults unless explicitly overridden. Configure schema provisioning deliberately. See Orleans Provider Packages in the server docs.

Protect stored connection grants

Host selects credential protection from effective persistence. In-memory localhost loses keys and grants on restart. SQL shares encrypted key XML; supply a private-key certificate to every silo and retain previous certificates during rotation. Read the integration guide.