Skip to main content
A Task represents a discrete unit of work within a workspace — it can be user-created, agent-created, or system-seeded as part of an onboarding or provider-scoring pipeline. Tasks belong to a single workspace, may be assigned to a Person, and may form a parent-child hierarchy (a task may have one parent task and many subtasks). They carry a rich status lifecycle, typed enums for executor and source, optional plan/step/scoring/subworkspace-candidate metadata in a JSONB field, and a polymorphic references array that links them to invoices, companies, people, documents, transactions, providers, or calendar months.

API operations

Data model

Attributes

Relationships

System-computed

  • task_id is generated by gen_random_uuid() at INSERT via MikroORM defaultRaw; unique constraint enforced at DB level.
  • created_at is set by MikroORM onCreate lifecycle hook to new Date(); never subsequently updated.
  • updated_at is set by MikroORM onCreate and refreshed on every UPDATE via onUpdate lifecycle hook.
  • deleted_at is the soft-delete sentinel. All active queries must filter deleted_at IS NULL. Both partial unique indexes (uniq_tasks_active_provider_company and uq_tasks_subworkspace_candidate_dedup) include deleted_at IS NULL so hard-deleting a row releases its dedup slot.
  • status defaults to ‘open’ (TaskStatusEnum.OPEN) at entity construction; the native PG enum task_status_enum enforces allowed values at the DB level.
  • source defaults to ‘user’ (TaskSourceEnum.USER); overridden to ‘agent’ by the scoring and onboarding pipelines.
  • priority defaults to ‘medium’ (TaskPriorityEnum.MEDIUM); nullable so it can be intentionally unset.
  • references defaults to [] (empty array). The first provider-typed entry is extracted to provider_ref_id and the first company-typed entry to company_ref_id by TaskService.createTask. These two denormalized columns must stay in sync with the references array via service-layer discipline (no DB trigger enforces this).
  • history defaults to [] (empty array). Entries are appended by the service layer on every state transition (status_changed, assigned, comment). No DB trigger; the service is the sole writer.
  • Partial unique index uniq_tasks_active_provider_company on (workspace_pk, provider_ref_id, company_ref_id) WHERE deleted_at IS NULL AND status IN (‘open’,‘ice_log’,‘blocked’) AND provider_ref_id IS NOT NULL AND company_ref_id IS NOT NULL AND parent_task_pk IS NULL. Closes the concurrent-worker dedup race for root scoring tasks. Sub-tasks (parent_task_pk IS NOT NULL) are excluded by design.
  • Partial unique index uq_tasks_subworkspace_candidate_dedup on (workspace_pk, (plan_meta->>‘dedup_key’)) WHERE deleted_at IS NULL AND plan_meta->>‘kind’ = ‘subworkspace-candidate’ AND plan_meta->>‘dedup_key’ IS NOT NULL. Prevents re-emission of duplicate subworkspace-candidate tasks for the same canonical identity. CANCELLED rows are still covered; only hard-deleted rows release the slot.
  • Composite index idx_tasks_workspace_status on (workspace_pk, status) for efficient status-filtered workspace list queries.
  • GIN index idx_tasks_references_gin on references JSONB column (jsonb_path_ops) for fast reference-type filter and dedup lookups.
  • plan_meta is a discriminated-union JSONB blob. The ‘kind’ discriminant determines the valid shape. The rendering layer narrows on plan_meta.kind to select the correct chat card or CTA component.
  • done_at is set by the service layer when transitioning status to ‘done’; it is not a DB-generated column.
  • conversation_thread_id is a soft UUID reference to a ChatConversation; there is no FK constraint so deleting the conversation does not affect the task.

Example

Source: apps/api/src/database/entities/Task.ts · domain: workspace · tier: Activity