MODULE 04 · LESSON 4.1
Persist agent state and shared application records
Choose between private agent state and shared typed records.
Lesson 16 of 86 · FabrCore 2.0
Overview
Private state belongs to one agent's lifecycle. Typed entity storage is useful when clients, services or multiple agents share application records. Both follow the configured Orleans storage lifetime; neither is the same as long-term semantic Memory.
State follows ownership
A private state object can hold the assistant's current investigation or last processed request ID. Only that agent needs to manage its lifecycle. A shared ServiceRequest record belongs in typed storage when a web page, API or another agent must read it independently. Neither choice automatically adds the data to a model prompt; you decide what context to supply.
Persistence is a provider property
Writing a state object through the storage API means it was handed to the configured provider. In standalone memory mode that provider disappears with the process. A durable provider can restore data on later activation, but your serialized shape must still be compatible with the new code. Keep business record IDs separate from chat thread IDs so changing the UI does not change record ownership.
Save and reload application data
- Use GetStateAsync or TryGetStateAsync to load an agent-specific draft/checkpoint. Use SetState and FlushStateAsync to persist it deliberately.
- Use IFabrCoreStorageProvider for typed values; use the principal-scoped Host API when acting for a particular owner rather than a host-internal system partition.
- Version your record shape and handle missing or unreadable persisted JSON with an explicit migration/reset decision.
public interface IFabrCoreStorageProvider
{
Task<T?> GetAsync<T>(string container, string entityKey, CancellationToken cancellationToken = default);
Task UpsertAsync<T>(string container, string entityKey, T value, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(string container, string entityKey, CancellationToken cancellationToken = default);
}
// Read state (returns default if not found, null, or undefined)
var stats = await GetStateAsync<ConversationStats>("stats");
// Safe read for migration-prone or resettable state
var stateRead = await TryGetStateAsync<ConversationStats>("stats");
if (!stateRead.Succeeded)
{
logger.LogWarning(
stateRead.Error,
"Resetting unreadable state key {Key}; stored kind was {ValueKind}",
stateRead.Key,
stateRead.ValueKind);
RemoveState(stateRead.Key);
stats = new ConversationStats();
}
// Read or create with factory
var prefs = await GetStateOrCreateAsync("preferences", () => new UserPreferences
{
Language = "en",
Theme = "dark"
});
// Write state (buffered in memory)
prefs.Theme = "light";
SetState("preferences", prefs);
// Remove a key
RemoveState("old-key");
// Persist all pending changes to Orleans storage
await FlushStateAsync();
// Check if key exists
var hasPrefs = await HasStateAsync("preferences");
public interface IFabrCoreStorageProvider
{
Task<T?> GetAsync<T>(string container, string entityKey, CancellationToken cancellationToken = default);
Task UpsertAsync<T>(string container, string entityKey, T value, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(string container, string entityKey, CancellationToken cancellationToken = default);
}
Distinguish reactivation from process recovery
- Store a recognizable value such as LastRequestId=SR-1042 using the illustrated state pattern, then load it through the same agent or storage key. Confirm the value matches.
- Exercise reactivation using your supported lifecycle path and inspect whether the agent restores the saved value. Do not use reset or eviction for this test: those actions can intentionally clear data.
- With standalone memory storage, stop the entire Host and restart it. Expect process-local state to be gone. Repeat the recovery test after configuring durable storage in module 8.
This separates a successful write from durable recovery. The storage key, owning principal, provider and process lifetime all participate in whether a value comes back.
If the result is different
Do not store high-volume evidence in ordinary agent state. A standalone restart loses the in-memory provider's values.
Go deeper
Explore the related documentation.