> For the complete documentation index, see [llms.txt](https://flow-core.gitbook.io/flow-core-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://flow-core.gitbook.io/flow-core-docs/documentation/api-extension-guide/persistence.md).

# Persistence

## Stability

Supported Extension API Level: Advanced

## Goal

Understand how blackboard data and prefab identity are stored and restored.

## Scope

* Blackboard data and runtime cache.
* Prefab instance lifecycle via GameObjectGUID.

## Blackboard persistence (core rules)

* Blackboard and GlobalBlackboard use GUIDs for stable storage.
* SceneBlackboard can have multiple instances per scene; SceneBlackboard.Current returns the most recently registered one.
* Persistence is gated by each blackboard’s `persistenceEnabled` flag.
* Transform snapshots restore **local** transform when the saved parent can be resolved; otherwise they fall back to world transform.
* Object values on blackboard are canonicalized as Direct in runtime/editor storage; reference wrappers are treated as compatible identity-oriented payloads during save/load.
* Persistence and restore are driven by the bucket codecs: when persistence is enabled, object entries serialize to reference-compatible payloads and resolve back to Direct on load.

## Blackboard Persistence Handlers

* Scene/local `Blackboard` instances support extra persistence handlers in addition to bucket data.
* `GlobalBlackboard` does not use the handler list.
* A handler identity is `TypeId + InstanceId`.
* `InstanceId` identifies one configured handler entry; it is not the handler type.
* If `targetOverride == null`, the handler resolves to `blackboard.gameObject`.
* Save writes one payload per enabled handler.
* Load restores bucket data first, then matches handlers by `InstanceId`, and finally verifies `TypeId`.
* A single handler failure does not abort the whole Blackboard restore.

## Built-in handlers

* `Persist Local Process` and `Persist Local Transform` are now represented as built-in handlers.
* New Blackboard instances still get the default built-in behavior through those handlers.
* Older snapshots remain readable; legacy process/transform data is mapped back into the built-in restore path.

## FlowCoreProcess / Persist Local Process

* `Persist Local Process` is a built-in handler for local `FlowCoreProcess` state.
* It is a coarse-grained local process restore feature. It is not a general breakpoint-resume system.
* The current process persistence path stores process identity, `StackRoots`, `FsmStates`, and `BtRoots`. On load, that snapshot becomes the pending process snapshot applied after runtime init.
* Load cancels current chains, restores stack root/layer structure, then re-enters saved FSM states and BT roots through the current runtime restore path.
* This restores local runtime structure and active roots/states. It does not guarantee returning to the exact instruction, condition, or async continuation that was active at save time.

### Current non-goals

* Exact call-chain breakpoint restore.
* Current instruction or condition index inside a node.
* Async wait internals such as remaining seconds, remaining frames, or signal waiter registrations.
* Exact reconstruction of transient `FlowContext` data for a suspended chain.
* Reliable one-to-one continuation for high-churn logic windows with rapid branching or dense event cascades.

### Operational guidance

* Recommended use: safe houses, bases, camps, hubs, post-cutscene handoff, or other stable low-interaction save windows.
* Avoid save/load during combat, rapid dialogue branching, dense Trigger cascades, or strongly time-sensitive scripted sequences.
* Prefer opening save/load only after critical logic has settled into a stable state.
* If a project requires exact logic breakpoint continuation, treat that as a separate custom runtime/persistence feature beyond the current `Persist Local Process` scope.

## Custom extension

* Create a custom extension by inheriting `BlackboardPersistenceHandler`.
* Implement `TrySave` and `TryLoad` to read and restore the exact component state you need.
* Use the provided `GameObject` argument as the lookup root for your target component or sub-structure.
* Prefer one handler per clear responsibility instead of one handler that snapshots an entire object graph.
* Use `DataVersion` to evolve your custom payload format safely over time.

Example: minimal custom handler.

```csharp
using System.IO;
using UnityEngine;

namespace TwoCatsCode.FlowCore
{
    [System.Serializable]
    public sealed class RigidbodyVelocityPersistenceHandler : BlackboardPersistenceHandler
    {
        public override bool TrySave(Blackboard blackboard, GameObject target, BinaryWriter writer)
        {
            if (target == null || writer == null || !target.TryGetComponent<Rigidbody>(out var body))
                return false;

            writer.Write(body.velocity.x);
            writer.Write(body.velocity.y);
            writer.Write(body.velocity.z);
            return true;
        }

        public override bool TryLoad(Blackboard blackboard, GameObject target, BinaryReader reader, int dataVersion)
        {
            if (target == null || reader == null || !target.TryGetComponent<Rigidbody>(out var body))
                return false;

            body.velocity = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
            return true;
        }
    }
}
```

## Handler instances

* A single Blackboard can contain multiple handlers of the same concrete type.
* Different instances can target different GameObjects or carry different internal configuration.
* Handler list order is execution order.
* Matching does not depend on type alone; it depends on the configured handler instance.

Example: multiple instances of one handler type.

```csharp
// Same handler type, different configured entries:
// - Handler A -> targetOverride = PlayerRoot
// - Handler B -> targetOverride = VehicleRoot
// They need different InstanceId values so cached payloads can match them independently.
```

## Authoring and recovery

* `InstanceId` is auto-generated for a new handler entry.
* It can still be edited manually when you need to align a handler with older cached data.
* `InstanceId` must remain unique inside one Blackboard.
* Inspector can surface cached ids of the same `TypeId` as recovery candidates.
* Manual recovery means pointing the current handler entry back to the cached `InstanceId` you want to load.

## Non-serializable value types

* Unity may not serialize some structs. Those entries are runtime-only.
* They are skipped by cache serialization and will not survive domain reloads.
* Treat non-serializable types as configuration-only. Avoid runtime mutation; rebuilds or re-clones can overwrite their values.
* Use Custom Type Generator to create a wrapper + serializer when persistence is required.

## SceneBlackboard stable resolution

* SceneBlackboardReference stores scene asset GUID (path/build index as fallback).
* `Resources/FlowSceneGuidRegistry.asset` maps scene GUIDs (auto-generated in Editor).
* Renaming or moving scenes does not break references if the registry is updated.

## SceneReference (Blackboard value)

* SceneReference is a serializable Blackboard value for scene targeting.
* Stores scene GUID + path + build index; resolves in that order.
* Supports Save/Load because it serializes stable identifiers, not Scene handles.

## Runtime cache

* FlowCore.Save / FlowCore.Load serialize the runtime cache.
* Use FlowCore.LoadAsync for non-blocking loads with progress callbacks.
* `int slotId` uses the configured `SaveExtension` and writes `slot_{slotId}.{extension}`.
* `string slot` is a relative file name or subpath under the configured save folder. If it does not already end with `.txt`, FlowCore appends `.txt`.
* `string slot` never accepts absolute paths and cannot escape the configured save folder.
* Compression and encryption are controlled by FlowCoreSettings.
* `ReloadSavedScenesOnLoad` restores the saved main scene from scene metadata.
* `ReloadSavedAdditionalScenesOnLoad` controls whether saved additive scenes are also restored. When it is off, `FlowCore.Load` / `LoadAsync` can still restore the saved main scene without reloading saved additive scenes.
* `LoadMainScene` / `LoadAdditiveScenes` remain the fallback scene list when `ReloadSavedScenesOnLoad` is disabled.
* Export/Import can move cache data without file IO.

Note: When Load must wait for deferred scene restore, the synchronous Load stays "in progress" until restore finishes. Prefer LoadAsync for user-facing flows. Note: FlowCore.OnLoadSucceeded / FlowCore.OnLoadFailed fire when a load has fully applied (including deferred restores). Note: A common duplicate additive load failure mode is enabling `ReloadSavedAdditionalScenesOnLoad` while startup logic, Flow instructions, or scene controllers also load the same additive scene. Keep the `Reload Saved Additional Scenes` setting off in those projects, and turn it on only when the save file is the authoritative source of additive scene state.

## Scene Stack restore timing

* `FlowCore.Load` and `FlowCore.LoadAsync` wait for Unity scene reload operations and any active Scene Stack transaction before applying cached runtime state.
* `ReadyToActivate` is treated as a busy Scene Stack state. Manual activation must finish through `Complete Pending Scene Stack Switch` before prefab and Blackboard restore apply.
* After the scene barrier completes, Flow Core yields one frame, scans `GameObjectGuid` registrations, applies scene object deletion tombstones, restores prefab instances once, then applies Blackboard cache data.
* If Scene Stack reaches `Failed` while a load is waiting for the barrier, Flow Core aborts the pending restore instead of applying prefab or Blackboard cache to a partial scene set.
* Save files do not currently store a `SceneStackAsset` identity. Scene Stack support in this path is restore timing, not semantic stack persistence.

## Scene Object Deletion Persistence

* Deletion Persistence is an opt-in `GameObjectGuid` setting for scene-authored objects that can be destroyed during Play Mode.
* Use it when a scene object is part of the authored scene, but gameplay can break, collect, consume, or otherwise destroy it, and that deletion should survive save/load.
* It only applies to `GameObjectGuid` components whose `Origin` is `SceneObject`.
* It does not apply to `RuntimeInstance` objects or `PrefabAsset` objects. Runtime-created prefab instances are represented by the prefab instance lifecycle snapshot instead.
* When an eligible object is destroyed during Play Mode, Flow Core records a deletion tombstone for that scene object GUID.
* During load, deletion tombstones are applied before runtime prefab instances are restored and before Blackboard cache data is applied.
* Scene unload, application quit, and restore passes are guarded so normal teardown is not recorded as a gameplay deletion.

Authoring checklist:

1. Add or keep a `GameObjectGuid` on the scene object.
2. Confirm the component `Origin` is `SceneObject`.
3. Enable `Deletion Persistence` on objects whose runtime destruction should be permanent after load.
4. Leave it disabled for ordinary objects that may be destroyed as part of scene unload, temporary setup, or resettable gameplay.

## Blackboard OnSet suppression during restore

* During cache apply and prefab restore, Blackboard OnSet callbacks are suppressed to avoid false triggers.
* Use `Blackboard.SuppressOnSet()` when you perform manual restore or bulk edits that should not notify observers.
* When the suppression scope is disposed, callbacks resume and `Blackboard.OnSetSuppressionEnded` is fired.
* Prefer `Blackboard.SubscribeOnSet(key, onSet, onSuppressionEnded)` to refresh baseline caches when suppression ends; dispose the subscription to remove both handlers.
* If a system caches baseline values, refresh them on `Blackboard.OnSetSuppressionEnded` to avoid stale comparisons.

## FlowCoreProcess restore timing

* Local FlowCoreProcess restoration waits for its own Awake trigger chain to complete, then applies immediately.
* Async waits inside Awake delay completion and can overwrite restored state.
* For deterministic restore, keep Awake synchronous; move long waits to Start or later.

## Save/Load triggers (Flow Graph)

* On Cache: fires before FlowCore.CacheRuntimeState (pre-cache preparation before save).
* On Save: fires before FlowCore.Save completes (after On Cache).
* On Load: fires after cache apply during Load; LoadAsync waits for these triggers to finish.
* On Cache Apply: fires after On Load (finalize post-apply handoff).
* On Load Failed: fires when a load fails (fallback/recovery).

Note: FlowCore.Load is synchronous and does not wait for async instructions inside triggers. Use LoadAsync if you need to await them.

### On Cache Apply timing notes

* On Cache Apply fires after cached data is applied to runtime objects (prefab restore + blackboard apply).
* Scene switches can apply cache per scene via the prefab lifecycle bridge; On Cache Apply fires after that apply as well.
* During FlowCore.Load / LoadAsync, On Cache Apply is fired after On Load to avoid double-calling.

Example: save/load with a safety scan.

```csharp
using TwoCatsCode.FlowCore;

public static class SaveExample
{
    public static void SaveSlot(int slotId)
    {
        FlowCore.ScanGameObjectGuids();
        FlowCore.Save(slotId);
    }

    public static void LoadSlot(int slotId)
    {
        FlowCore.Load(slotId);
    }
}
```

Example: async load with progress.

```csharp
using TwoCatsCode.FlowCore;

public static class AsyncLoadExample
{
    public static void LoadSlotAsync(int slotId)
    {
        FlowCore.LoadAsync(
            slotId,
            progress => { },
            result => { });
    }
}
```

Example: named save/load with `.txt` normalization.

```csharp
using TwoCatsCode.FlowCore;

public static class NamedSaveExample
{
    public static void SaveProfile()
    {
        FlowCore.Save("profile1"); // -> profile1.txt
        FlowCore.Save("profiles/a/profile1.save"); // -> profiles/a/profile1.save.txt
    }

    public static void LoadProfile()
    {
        FlowCore.Load("profile1.txt");
        FlowCore.LoadAsync("profiles/a/profile1");
    }
}
```

Flow instructions "Load" and "Load (Result)" include an Async toggle and a Progress output when Async is enabled. Additional named-slot instructions are available as "Save (Slot Name)", "Save (Slot Name, Result)", "Load (Slot Name)", and "Load (Slot Name, Result)".

Example: export/import cache in memory.

```csharp
using TwoCatsCode.FlowCore;

public static class CacheExample
{
    public static byte[] ExportCache()
    {
        return FlowCore.ExportRuntimeCache();
    }

    public static void ImportCache(byte[] data)
    {
        FlowCore.ImportRuntimeCache(data, applyImmediately: true);
    }
}
```

### Blackboard cache subset operations

* `FlowBlackboardCacheMatchMode` defines how a GUID list is interpreted.
* `MatchedOnly` means "operate only on the listed blackboard GUIDs."
* `ExceptMatched` means "operate on every cached blackboard GUID except the listed ones."
* `FlowCore.ClearBlackboardRuntimeCache(IEnumerable<string>, FlowBlackboardCacheMatchMode)` removes cached blackboard entries from the current runtime cache.
* `FlowCore.ExportBlackboardRuntimeCache(IEnumerable<string>, FlowBlackboardCacheMatchMode)` exports a blackboard-only cache package as `byte[]`.
* `FlowCore.ReplaceBlackboardRuntimeCache(byte[])` clears the current blackboard cache, then imports the provided blackboard cache package.
* `FlowCore.AppendBlackboardRuntimeCache(byte[])` merges the provided blackboard cache package into the current blackboard cache and overwrites same-GUID entries.
* These APIs affect blackboard cache only. Prefab runtime cache is unchanged.
* These APIs do not call `FlowCore.CacheRuntimeState()` for you. Cache first if you need the latest runtime state.
* `ReplaceBlackboardRuntimeCache` and `AppendBlackboardRuntimeCache` mutate cached data only. They do not immediately write values back into live blackboard objects.
* Empty matches export to an empty payload. Empty or invalid import payloads return `false` and leave the current blackboard cache unchanged.
* Use this feature for subset backup, selective cleanup, and cache migration or merge.

Flow/SaveLoad instructions for the same feature:

* `Clear Blackboard Cache`
* `Export Blackboard Cache`
* `Replace Blackboard Cache Package`
* `Append Blackboard Cache Package`

Example: export a GUID-filtered subset in C#.

```csharp
using TwoCatsCode.FlowCore;

public static class BlackboardSubsetCacheExample
{
    public static byte[] ExportSubset()
    {
        FlowCore.CacheRuntimeState();

        string[] guidSubset =
        {
            "enemy-blackboard-a",
            "enemy-blackboard-b"
        };

        return FlowCore.ExportBlackboardRuntimeCache(
            guidSubset,
            FlowBlackboardCacheMatchMode.MatchedOnly);
    }

    public static bool ReplaceSubset(byte[] payload)
    {
        return FlowCore.ReplaceBlackboardRuntimeCache(payload);
    }
}
```

Example: graph-side package flow.

1. Use `Export Blackboard Cache` with a `List<string>` GUID list and `Mode = MatchedOnly` or `ExceptMatched`.
2. Store the `Payload` output as a `List<byte>` blackboard value or pass it through another graph.
3. Use `Replace Blackboard Cache Package` to replace the current cached subset, or `Append Blackboard Cache Package` to merge it.
4. If live scene values also need to change, run the normal load/apply path after the package import.

### Single Blackboard import/export

* `BlackboardPersistence.ExportBlackboardData(Blackboard)` exports one scene/local blackboard to `byte[]`.
* `BlackboardPersistence.ImportBlackboardData(Blackboard, byte[])` imports one scene/local blackboard from `byte[]`.
* `SceneBlackboard` uses the `Blackboard` overload because it inherits from `Blackboard`.
* `BlackboardPersistence.ExportBlackboardData(GlobalBlackboard)` and `ImportBlackboardData(GlobalBlackboard, byte[])` do the same for global blackboards.
* `Blackboard` export/import respects `PersistenceEnabled` and includes handler snapshots in addition to bucket data.
* Single-blackboard import/export applies directly to the target object and does not read or write the global runtime cache.

Example: export/import one blackboard.

```csharp
using TwoCatsCode.FlowCore;

public static class SingleBlackboardExample
{
    public static byte[] Export(Blackboard blackboard)
    {
        return BlackboardPersistence.ExportBlackboardData(blackboard);
    }

    public static bool Import(Blackboard blackboard, byte[] data)
    {
        return BlackboardPersistence.ImportBlackboardData(blackboard, data);
    }
}
```

## Prefab instance lifecycle

* GameObjectGUID provides stable identity for prefab instances.
* Prefab tracking is gated by FlowCoreSettings.EnablePrefabLifeCycle.
* Auto-scan can be disabled; call `FlowCore.ScanGameObjectGuids()` before Save if disabled.
* Runtime-created prefab instances are restored from prefab lifecycle snapshots.
* Scene-authored objects that should stay destroyed after runtime destruction use Deletion Persistence instead of prefab lifecycle restoration.

## Scene GUID pooling (runtime)

* SceneGameObjectGuidPoolManager provides per-scene pooling for runtime prefab instances (Addressable key).
* Spawn always assigns a new runtime GUID to avoid stale references.
* Despawn unregisters the instance from Prefab LifeCycle and GameObjectRegistry before pooling.
* Pooled instances are excluded from auto-scan and fallback GUID resolution.
* Restore does not use pooling; prefab restore still instantiates from snapshots.
* Pool expansion size is controlled by FlowCoreSettings.PrefabPoolExpansionCount.
* Inspector shows bucket stats in Play Mode.
* Managers auto-create per loaded scene in Play Mode (skip DontDestroyOnLoad).

Example: spawn/despawn (world).

```csharp
var instance = SceneGameObjectGuidPoolManager.Spawn("Enemy/Grunt", position, rotation);
SceneGameObjectGuidPoolManager.Despawn(instance);
```

Example: spawn local to a parent.

```csharp
var instance = SceneGameObjectGuidPoolManager.SpawnLocal(
    "Enemy/Grunt",
    localPosition,
    localRotation,
    parentTransform);
```

### Flow nodes (Pool Manager)

* Instructions: Pool Spawn, Pool Despawn, Pool Clear Scene, Pool Clear Active Scene.
* Conditions: Pool Manager Exists, Pool Has Bucket, Pool Count Compare (Pooled/Active/Total), Pool Instance Check (IsInPool/IsPooled/IsActivePooled).
* Spawn accepts Prefab Reference (Addressable key), GameObject, GameObjectReference, or Prefab (Blackboard Prefab/AssetReference). Prefab uses AssetReference.Address when available; otherwise GameObject/Reference/Prefab use PrefabPath as the prefab reference.
* Defaults: Pool Spawn Prefab Source = Prefab. Other Pool instructions/conditions default to GameObjectReference.
* Failure Policy supports LogAndContinue (default), ThrowException, and Silent.
* Safety notes:
  * Despawn on a non-pooled GameObject will auto-destroy by default; disable "Destroy If Not Pooled" to keep the old warn-and-fail behavior.
  * Destroying a pooled instance will clean pool tracking (bucket/active counts), but it will NOT remove the instance from the runtime cache snapshot. Use Despawn for normal flow; call `FlowCore.UnregisterPrefabInstance(...)` if you need to permanently remove a pooled instance from the cache before Destroy.

## Failure modes

* Duplicate GUIDs → data collision.
* Missing registry asset → scene references fail.
* Disabled persistence → data will not save.
* Changed `InstanceId` → older handler cache no longer matches the current entry.
* Missing `Target Override` object or missing target component → that handler is skipped during restore.
* Exception inside a custom handler → that handler is skipped; other bucket and handler data can still restore.
* Destroyed scene objects return after load → Deletion Persistence is disabled, the object has no `GameObjectGuid`, or the GUID origin is not `SceneObject`.
* `Persist Local Process` used inside unstable runtime windows → restore may re-enter a coarse state boundary instead of the exact saved logic breakpoint.
* Save/load during combat, dialogue churn, or dense event chains → skipped reactions, repeated actions, broken waits, or scripted progression drift can occur.

## Related

* [FlowCore Settings](/flow-core-docs/documentation/reference/flow-core-settings.md)
* [Lifecycle and Dataflow](/flow-core-docs/documentation/runtime-guide/lifecycle-and-dataflow.md)
* [Blackboard UI](/flow-core-docs/documentation/editor-guide/blackboard-ui.md)
* [Pitfalls](/flow-core-docs/documentation/troubleshooting/pitfalls.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://flow-core.gitbook.io/flow-core-docs/documentation/api-extension-guide/persistence.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
