Portal Community

Mechanism 1 — Explicit Clear from Executor

An executor can clear its own pinned data by calling ctx.ExecutionMemory.ClearPinnedDataAsync. This deletes the row from Process_NodePinnedData.

// ResetableNode.cs — clears pinned data when reset input is received
public override async Task<NodeExecutionResult> ExecuteAsync(
    NodeExecutionContext ctx,
    CancellationToken ct)
{
    var shouldReset = ctx.ExecutionMemory.GetNodeOutput<bool>("reset-trigger-node");

    if (shouldReset)
    {
        await ctx.ExecutionMemory.ClearPinnedDataAsync(ctx.NodeId, ct);
        ctx.Observability.Logger.LogInformation("Pinned data cleared — starting fresh");
        return NodeExecutionResult.Success(output: null);
    }

    var previous = await ctx.ExecutionMemory.GetPinnedDataAsync<AccumulatedData>(ctx.NodeId, ct);
    // ... continue with previous data
}

IExecutionMemory — Clear Methods

// IExecutionMemory.cs (clear methods)
public interface IExecutionMemory
{
    // Delete pinned data for a specific node
    Task ClearPinnedDataAsync(string nodeId, CancellationToken ct = default);

    // Delete ALL pinned data for this process (use with care)
    Task ClearAllPinnedDataAsync(CancellationToken ct = default);
}

Mechanism 2 — TTL-Based Expiry

Pinned data can be saved with a time-to-live by using the SaveAsync overload that accepts an expiresAt parameter. The background cleanup job removes expired rows.

// PinnedDataService.SaveAsync with TTL (called by executor result handling in BaseNodeExecutor)
// The TTL overload of NodeExecutionResult.Success is used to set expiry:

// Executor returns pinned data with 24-hour expiry
return NodeExecutionResult.Success(
    output     : result,
    pinnedData : new CachedApiResult { Data = result, CachedAt = DateTime.UtcNow },
    pinnedDataTtl : TimeSpan.FromHours(24)  // Optional TTL argument
);

TTL Expiry Behaviour

ScenarioBehaviour
ExpiresAt in futureGetPinnedDataAsync returns the data normally
ExpiresAt in pastGetPinnedDataAsync returns null (as if no data exists)
Background cleanup jobRuns every 24h (configurable) — deletes rows where ExpiresAt < NOW()
New write to expired nodeUpsert updates the row and sets new ExpiresAt — row is not deleted first

Mechanism 3 — Cascade Delete on Node Removal

When a node is removed from the workflow definition and the workflow is saved, the WorkflowDefinitionService computes the diff and calls PinnedDataService.ClearForRemovedNodesAsync. This prevents orphaned pinned data accumulating for nodes that no longer exist in the workflow.

// WorkflowDefinitionService.cs (simplified)
public async Task SaveDefinitionAsync(int processId, SerializedWorkflow definition, CancellationToken ct)
{
    var previous = await _definitionRepo.GetAsync(processId, ct);
    var removedNodeIds = GetRemovedNodeIds(previous, definition);

    // Persist the new definition
    await _definitionRepo.SaveAsync(processId, definition, ct);

    // Cascade: clear pinned data for nodes no longer in the workflow
    if (removedNodeIds.Any())
    {
        await _pinnedDataService.ClearForNodesAsync(processId, removedNodeIds, ct);
    }
}

Clearing via Admin API

Tenant administrators can clear pinned data from outside the execution engine via the management API.

// Management API — clear pinned data for a specific node
DELETE /api/processes/{processId}/nodes/{nodeId}/pinned-data

// Clear all pinned data for a process
DELETE /api/processes/{processId}/pinned-data
Clearing pinned data does NOT stop the current execution. ClearPinnedDataAsync only removes the database row. The current execution continues normally. If the node writes new pinned data in the same run (by returning non-null pinnedData), a new row is inserted immediately after.