Skip to content

@voltagent/[email protected]

Choose a tag to compare

@voltagent-bot voltagent-bot released this 25 Oct 17:10
· 51 commits to main since this release
825aeb2

Patch Changes

  • #740 bac1f49 Thanks @marinoska! - Stable fix for the providerMetadata openai entries normalization bug: #718

  • #738 d3ed347 Thanks @omeraplak! - fix: persist workflow execution timeline events to prevent data loss after completion - #647

    The Problem

    When workflows executed, their timeline events (step-start, step-complete, workflow-complete, etc.) were only visible during streaming. Once the workflow completed, the WebSocket state update would replace the execution object without the events field, causing the timeline UI to reset and lose all execution history. Users couldn't see what happened in completed or suspended workflows.

    Symptoms:

    • Timeline showed events during execution
    • Timeline cleared/reset when workflow completed
    • No execution history for completed workflows
    • Events were lost after browser refresh

    The Solution

    Backend (Framework):

    • Added events, output, and cancellation fields to WorkflowStateEntry interface
    • Modified workflow execution to collect all stream events in memory during execution
    • Persist collected events to workflow state when workflow completes, suspends, fails, or is cancelled
    • Updated all storage adapters to support the new fields:
      • LibSQL: Added schema columns + automatic migration method (addWorkflowStateColumns)
      • Supabase: Added schema columns + migration detection + ALTER TABLE migration SQL
      • Postgres: Added schema columns + INSERT/UPDATE queries
      • In-Memory: Automatically supported via TypeScript interface

    Frontend (Console):

    • Updated WorkflowPlaygroundProvider to include events when converting WorkflowStateEntryWorkflowHistoryEntry
    • Implemented smart merge strategy for WebSocket updates: Use backend persisted events when workflow finishes, keep streaming events during execution
    • Events are now preserved across page refreshes and always visible in timeline UI

    What Gets Persisted

    // In WorkflowStateEntry (stored in Memory V2):
    {
      "events": [
        {
          "id": "evt_123",
          "type": "workflow-start",
          "name": "Workflow Started",
          "startTime": "2025-01-24T10:00:00Z",
          "status": "running",
          "input": { "userId": "123" }
        },
        {
          "id": "evt_124",
          "type": "step-complete",
          "name": "Step: fetch-user",
          "startTime": "2025-01-24T10:00:01Z",
          "endTime": "2025-01-24T10:00:02Z",
          "status": "success",
          "output": { "user": { "name": "John" } }
        }
      ],
      "output": { "result": "success" },
      "cancellation": {
        "cancelledAt": "2025-01-24T10:00:05Z",
        "reason": "User requested cancellation"
      }
    }

    Migration Guide

    LibSQL Users

    No action required - migrations run automatically on next initialization.

    Supabase Users

    When you upgrade and initialize the adapter, you'll see migration SQL in the console. Run it in your Supabase SQL Editor:

    -- Add workflow event persistence columns
    ALTER TABLE voltagent_workflow_states
    ADD COLUMN IF NOT EXISTS events JSONB;
    
    ALTER TABLE voltagent_workflow_states
    ADD COLUMN IF NOT EXISTS output JSONB;
    
    ALTER TABLE voltagent_workflow_states
    ADD COLUMN IF NOT EXISTS cancellation JSONB;

    Postgres Users

    No action required - migrations run automatically on next initialization.

    In-Memory Users

    No action required - automatically supported.

    VoltAgent Managed Memory Users

    No action required - migrations run automatically on first request per managed memory database after API deployment. The API has been updated to:

    • Include new columns in ManagedMemoryProvisioner CREATE TABLE statements (new databases)
    • Run automatic column addition migration for existing databases (lazy migration on first request)
    • Update PostgreSQL memory adapter to persist and retrieve events, output, and cancellation fields

    Zero-downtime deployment: Existing managed memory databases will be migrated lazily when first accessed after the API update.

    Impact

    • ✅ Workflow execution timeline is now persistent and survives completion
    • ✅ Full execution history visible for completed, suspended, and failed workflows
    • ✅ Events, output, and cancellation metadata preserved in database
    • ✅ Console UI timeline works consistently across all workflow states
    • ✅ All storage backends (LibSQL, Supabase, Postgres, In-Memory) behave consistently
    • ✅ No data loss on workflow completion or page refresh
  • #743 55e3555 Thanks @omeraplak! - feat: add OperationContext support to Memory adapters for dynamic runtime behavior

    The Problem

    Memory adapters (InMemory, PostgreSQL, custom) had fixed configuration at instantiation time. Users couldn't:

    1. Pass different memory limits per generateText() call (e.g., 10 messages for quick responses, 100 for summaries)
    2. Access agent execution context (logger, tracing, abort signals) within memory operations
    3. Implement context-aware memory behavior without modifying adapter configuration

    The Solution

    Framework (VoltAgent Core):

    • Added optional context?: OperationContext parameter to all StorageAdapter methods
    • Memory adapters now receive full agent execution context including:
      • context.context - User-provided key-value map for dynamic parameters
      • context.logger - Contextual logger for debugging
      • context.traceContext - OpenTelemetry tracing integration
      • context.abortController - Cancellation support
      • userId, conversationId, and other operation metadata

    Type Safety:

    • Replaced any types with proper OperationContext type
    • No circular dependencies (type-only imports)
    • Full IDE autocomplete support

    Usage Example

    Dynamic Memory Limits

    import { Agent, Memory, InMemoryStorageAdapter } from "@voltagent/core";
    import type { OperationContext } from "@voltagent/core/agent";
    
    class DynamicMemoryAdapter extends InMemoryStorageAdapter {
      async getMessages(
        userId: string,
        conversationId: string,
        options?: GetMessagesOptions,
        context?: OperationContext
      ): Promise<UIMessage[]> {
        // Extract dynamic limit from context
        const dynamicLimit = context?.context.get("memoryLimit") as number;
        return super.getMessages(
          userId,
          conversationId,
          {
            ...options,
            limit: dynamicLimit || options?.limit || 10,
          },
          context
        );
      }
    }
    
    const agent = new Agent({
      memory: new Memory({ storage: new DynamicMemoryAdapter() }),
    });
    
    // Short context for quick queries
    await agent.generateText("Quick question", {
      context: new Map([["memoryLimit", 5]]),
    });
    
    // Long context for detailed analysis
    await agent.generateText("Summarize everything", {
      context: new Map([["memoryLimit", 100]]),
    });

    Access Logger and Tracing

    class ObservableMemoryAdapter extends InMemoryStorageAdapter {
      async getMessages(...args, context?: OperationContext) {
        context?.logger.debug("Fetching messages", {
          traceId: context.traceContext.getTraceId(),
          userId: args[0],
        });
        return super.getMessages(...args, context);
      }
    }

    Impact

    • Dynamic behavior per request without changing adapter configuration
    • Full observability - Access to logger, tracing, and operation metadata
    • Type-safe - Proper TypeScript types with IDE autocomplete
    • Backward compatible - Context parameter is optional
    • Extensible - Custom adapters can implement context-aware logic

    Breaking Changes

    None - the context parameter is optional on all methods.