Interface TaskStore

All Known Implementing Classes:
InMemoryTaskStore, JpaDatabaseTaskStore

public interface TaskStore
Storage interface for managing task persistence across the task lifecycle.

TaskStore is responsible for persisting task state including status updates, artifacts, message history, and metadata. It's called by DefaultRequestHandler and TaskManager to save task state as agents process requests and generate events.

Persistence Guarantees

Tasks are persisted:
  • After each status update event (SUBMITTED, WORKING, COMPLETED, etc.)
  • After each artifact is added
  • Before events are distributed to clients (ensures consistency)
  • Before push notifications are sent
Persistence happens synchronously before responses are returned, ensuring clients always see committed state.

Default Implementation

InMemoryTaskStore:
  • Stores tasks in ConcurrentHashMap
  • Also implements TaskStateProvider for queue lifecycle decisions
  • Thread-safe for concurrent operations
  • Tasks lost on application restart

Alternative Implementations

  • extras/task-store-database-jpa: JpaDatabaseTaskStore with PostgreSQL/MySQL persistence
Database implementations:
  • Survive application restarts
  • Enable task sharing across server instances
  • Typically also implement TaskStateProvider for integrated state queries
  • Support transaction boundaries for consistency

Relationship to TaskStateProvider

Many TaskStore implementations also implement TaskStateProvider to provide queue lifecycle management with task state information:

 @ApplicationScoped
 public class InMemoryTaskStore implements TaskStore, TaskStateProvider {
     // Provides both persistence and state queries
     public boolean isTaskFinalized(String taskId) {
         Task task = tasks.get(taskId);
         return task != null && task.status().state().isFinal();
     }
 }
 

CDI Extension Pattern


 @ApplicationScoped
 @Alternative
 @Priority(50)  // Higher than default InMemoryTaskStore
 public class JpaDatabaseTaskStore implements TaskStore, TaskStateProvider {
     @PersistenceContext
     EntityManager em;

     @Transactional
     public void save(Task task) {
         TaskEntity entity = toEntity(task);
         em.merge(entity);
     }
 }
 

Thread Safety

Implementations must be thread-safe. Multiple threads will call methods concurrently for different tasks. Concurrent save() calls for the same task must handle conflicts appropriately (last-write-wins, optimistic locking, etc.).

List Operation Performance

The list(ListTasksParams, ServerCallContext) method may need to scan and filter many tasks. Database implementations should:
  • Use indexes on contextId, status, lastUpdatedAt
  • Implement efficient pagination with stable ordering
  • Consider caching for frequently-accessed task lists

Exception Contract

All TaskStore methods may throw TaskStoreException or its subclasses to indicate persistence failures:

When to Throw TaskSerializationException

Use when task data cannot be serialized or deserialized:
  • JSON parsing errors during get() operations
  • JSON serialization errors during save() operations
  • Invalid enum values or missing required fields
  • Schema version mismatches after upgrades

When to Throw TaskPersistenceException

Use when the storage system fails:
  • Database connection timeouts
  • Transaction deadlocks
  • Connection pool exhausted
  • Disk full / quota exceeded
  • Database constraint violations
  • Insufficient permissions

Implementer Example


 @Override
 public void save(Task task, boolean isReplicated) {
     try {
         String json = objectMapper.writeValueAsString(task);
     } catch (JsonProcessingException e) {
         throw new TaskSerializationException(task.id(), "Failed to serialize task", e);
     }

     try {
         entityManager.merge(toEntity(json));
     } catch (PersistenceException e) {
         throw new TaskPersistenceException(task.id(), "Database save failed", e);
     }
 }
 

Exception Handling

MainEventBusProcessor catches TaskStore exceptions and wraps them in InternalError events for client distribution.
See Also:
  • Method Details

    • save

      void save(Task task, boolean isReplicated)
      Saves or updates a task.
      Parameters:
      task - the task to save
      isReplicated - true if this task update came from a replicated event, false if it originated locally. Used to prevent feedback loops in replicated scenarios (e.g., don't fire TaskFinalizedEvent for replicated updates)
      Throws:
      TaskSerializationException - if the task cannot be serialized to storage format (JSON parsing error, invalid field values, schema mismatch)
      TaskPersistenceException - if the storage system fails (database timeout, connection error, disk full)
      TaskStoreException - for other persistence failures not covered by specific subclasses
    • get

      @Nullable Task get(String taskId)
      Retrieves a task by its ID.
      Parameters:
      taskId - the task identifier
      Returns:
      the task if found, null otherwise
      Throws:
      TaskSerializationException - if the persisted task data cannot be deserialized (corrupted JSON, schema incompatibility)
      TaskPersistenceException - if the storage system fails during retrieval (database connection error, query timeout)
      TaskStoreException - for other retrieval failures not covered by specific subclasses
    • delete

      void delete(String taskId)
      Deletes a task by its ID.
      Parameters:
      taskId - the task identifier
      Throws:
      TaskPersistenceException - if the storage system fails during deletion (database connection error, transaction timeout, constraint violation)
      TaskStoreException - for other deletion failures not covered by specific subclasses
    • list

      ListTasksResult list(ListTasksParams params, @Nullable ServerCallContext context)
      List tasks with optional filtering and pagination.

      Authorization filtering: When a TaskAuthorizationProvider bean is present, implementations must call checkRead for each candidate task and exclude tasks for which the check returns false. The filtering should be applied before pagination so that page sizes are correct from the caller's perspective. If no provider is present, all tasks are returned.

      ⚠ Custom implementation warning: Returning unfiltered results bypasses the authorization model and can leak tasks belonging to other users. Custom implementations must apply per-task checkRead filtering before pagination. The TaskAuthorizationProvider should be declared as a CDI dependency and injected via the constructor or @Inject.

      Parameters:
      params - the filtering and pagination parameters
      context - the server call context (used for authorization filtering)
      Returns:
      the list of tasks matching the criteria with pagination info
      Throws:
      TaskSerializationException - if any persisted task data cannot be deserialized during listing (corrupted JSON in database)
      TaskPersistenceException - if the storage system fails during the list operation (database query timeout, connection error)
      TaskStoreException - for other listing failures not covered by specific subclasses